Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/quiet-tags-keep.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'prettier-plugin-svelte': patch
---

fix: don't drop comments in an opening tag without attributes, or containing `>`
68 changes: 63 additions & 5 deletions src/embed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -492,14 +492,45 @@ function walkAndAttach(
}

const last_attr = attrs[attrs.length - 1];
const opening_tag_end =
original_text && typeof node.end === 'number'
? original_text.indexOf('>', last_attr.end)
: -1;
const opening_tag_end = findOpeningTagEnd(
original_text,
last_attr.end,
node.end,
commentsByStart,
);

if (opening_tag_end >= 0 && opening_tag_end <= node.end) {
if (opening_tag_end >= 0) {
attachCommentsInRange(last_attr.end, opening_tag_end, last_attr, null, commentsByStart);
}
} else if (
'attributes' in node &&
Array.isArray(node.attributes) &&
typeof (node as any).name === 'string'
) {
// No attributes: comments between the tag name and `>` have nothing to attach to,
// so keep them on the element itself and let the element printer print them.
// Start after `this={...}` of <svelte:element>/<svelte:component>, whose comments
// are printed along with that expression.
const this_expression = (node as any).tag ?? (node as any).expression;
const tag_name_end = Math.max(
node.start + 1 + (node as any).name.length,
typeof this_expression?.end === 'number' ? this_expression.end : 0,
);
const opening_tag_end = findOpeningTagEnd(
original_text,
tag_name_end,
node.end,
commentsByStart,
);

if (opening_tag_end >= 0) {
for (const [start, comment] of commentsByStart) {
if (start >= tag_name_end && comment.end <= opening_tag_end) {
util.addDanglingComment(node, comment, undefined);
commentsByStart.delete(start);
}
}
}
}

// Recurse into children and block branches
Expand Down Expand Up @@ -550,3 +581,30 @@ function attachCommentsInRange(
}
}
}

/**
* Returns the position of the `>` that ends an opening tag, starting the search at `from`
* (which must be past the last attribute). Skips over comments, so that a `>` inside a
* comment isn't mistaken for the end of the tag. Returns -1 if not found.
*/
function findOpeningTagEnd(
original_text: string,
from: number,
limit: number,
commentsByStart: Map<number, any>,
): number {
if (!original_text || typeof limit !== 'number') return -1;

let pos = from;
while (pos < limit) {
const comment = commentsByStart.get(pos);
if (comment) {
pos = comment.end;
} else if (original_text[pos] === '>') {
return pos;
} else {
pos++;
}
}
return -1;
}
28 changes: 28 additions & 0 deletions src/print/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,3 +91,31 @@ export function printWithPrependedAttributeLine(
? [getAttributeLine(node, options), path.call(print)]
: '';
}

/**
* Prints the comments of an opening tag that has no attributes they could be attached to,
* e.g. `<div /* comment *\/>` (attached as dangling comments on the element in `embed`).
*/
export function printDanglingTagComments(node: ElementNode, bracketSameLine: boolean): Doc[] {
const { line, hardline, breakParent, dedent } = doc.builders;
const comments: any[] = ((node as any).comments ?? []).filter(
(comment: any) => !comment.leading && !comment.trailing,
);
const parts: Doc[] = [];

comments.forEach((comment, i) => {
comment.printed = true;
if (comment.type === 'Line') {
parts.push(line, '//' + comment.value.replace(/\r$/, ''), breakParent);
// Nothing but a line break may follow a line comment, so make sure
// the `>` doesn't end up on the same line
if (i === comments.length - 1 && bracketSameLine) {
parts.push(dedent(hardline));
}
} else {
parts.push(line, '/*' + comment.value + '*/');
}
});

return parts;
}
9 changes: 5 additions & 4 deletions src/print/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
isASTNode,
isPreTagContent,
printWithPrependedAttributeLine,
printDanglingTagComments,
replaceEndOfLineWith,
} from './helpers';
import {
Expand Down Expand Up @@ -224,10 +225,10 @@ export function print(path: AstPath, options: ParserOptions, print: PrintFn): Do
isDoctypeTag);

// Order important: print attributes first
const attributes = path.map(
printWithPrependedAttributeLine(node, options, print),
'attributes',
);
const attributes = [
...path.map(printWithPrependedAttributeLine(node, options, print), 'attributes'),
...printDanglingTagComments(node, bracketSameLine),
];
const attributeLine = getAttributeLine(node, options);
const possibleThisBinding =
node.type === 'SvelteComponent' && node.expression
Expand Down
14 changes: 14 additions & 0 deletions test/printer/samples/attribute-comment-without-attributes.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<p /* a > b , c */></p>
<br /* a > b , c */ />
<p /* comment */></p>
<br /* comment */ />
<div /* one */ /* two */>text</div>
<div class="foo" /* a > b */></div>
<div
class="foo" // a > b
></div>
<div
// line comment
></div>
<Component /* comment */ />
<svelte:element this={tag} /* a > b */></svelte:element>