Skip to content

Commit 92e2fc1

Browse files
feat: allow comments in tags (#17671)
Alternative to #17188. I prefer this syntax, it's lighter and feels much more natural to me. For me it's less about commenting things _out_ than about just, well... commenting — I frequently want to do this sort of thing: ```svelte <button // when the user clicks the button, the thing should happen onclick={doTheThing} >click me</button> ``` One difference between this and #17188 is that this doesn't add a node to the AST, just like comments in CSS/JS. Haven't decided if that's desirable or not. I think it's more correct (it's an AST, not a CST; HTML comments are different insofar as they _can_ represent 'real' nodes) but it might be less convenient when (for example) pretty-printing. ### Before submitting the PR, please make sure you do the following - [x] It's really useful if your PR references an issue where it is discussed ahead of time. In many cases, features are absent for a reason. For large changes, please create an RFC: https://github.com/sveltejs/rfcs - [x] Prefix your PR title with `feat:`, `fix:`, `chore:`, or `docs:`. - [x] This message body should clearly illustrate what problems it solves. - [x] Ideally, include a test that fails without this PR but passes with it. - [x] If this PR changes code within `packages/svelte/src`, add a changeset (`npx changeset`). ### Tests and linting - [x] Run the tests with `pnpm test` and lint the project with `pnpm lint` --------- Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com>
1 parent 2661513 commit 92e2fc1

File tree

9 files changed

+892
-37
lines changed

9 files changed

+892
-37
lines changed

.changeset/angry-suns-call.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'svelte': minor
3+
---
4+
5+
feat: allow comments in tags

packages/svelte/src/compiler/legacy.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,12 @@ export function convert(source, ast) {
101101
},
102102
instance,
103103
module,
104-
css: ast.css ? visit(ast.css) : undefined
104+
css: ast.css ? visit(ast.css) : undefined,
105+
// put it on _comments not comments because the latter is checked by prettier and then fails
106+
// if we don't adjust stuff accordingly in our prettier plugin, and so it would be kind of an
107+
// indirect breaking change for people updating their Svelte version but not their prettier plugin version.
108+
// We can keep it as comments for the modern AST because the modern AST is not used in the plugin yet.
109+
_comments: ast.comments?.length > 0 ? ast.comments : undefined
105110
};
106111
},
107112
AnimateDirective(node) {

packages/svelte/src/compiler/phases/1-parse/state/element.js

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -506,6 +506,15 @@ function read_static_attribute(parser) {
506506
* @returns {AST.Attribute | AST.SpreadAttribute | AST.Directive | AST.AttachTag | null}
507507
*/
508508
function read_attribute(parser) {
509+
/** @type {AST.JSComment | null} */
510+
// eslint-disable-next-line no-useless-assignment -- it is, in fact, eslint that is useless
511+
let comment = null;
512+
513+
while ((comment = read_comment(parser))) {
514+
parser.root.comments.push(comment);
515+
parser.allow_whitespace();
516+
}
517+
509518
const start = parser.index;
510519

511520
if (parser.eat('{')) {
@@ -702,6 +711,50 @@ function read_attribute(parser) {
702711
return create_attribute(tag.name, tag.loc, start, end, value);
703712
}
704713

714+
/**
715+
* @param {Parser} parser
716+
* @returns {AST.JSComment | null}
717+
*/
718+
function read_comment(parser) {
719+
const start = parser.index;
720+
721+
if (parser.eat('//')) {
722+
const value = parser.read_until(/\n/);
723+
const end = parser.index;
724+
725+
return {
726+
type: 'Line',
727+
start,
728+
end,
729+
value,
730+
loc: {
731+
start: locator(start),
732+
end: locator(end)
733+
}
734+
};
735+
}
736+
737+
if (parser.eat('/*')) {
738+
const value = parser.read_until(/\*\//);
739+
740+
parser.eat('*/');
741+
const end = parser.index;
742+
743+
return {
744+
type: 'Block',
745+
start,
746+
end,
747+
value,
748+
loc: {
749+
start: locator(start),
750+
end: locator(end)
751+
}
752+
};
753+
}
754+
755+
return null;
756+
}
757+
705758
/**
706759
* @param {string} name
707760
* @returns {any}

packages/svelte/src/compiler/print/index.js

Lines changed: 74 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,17 @@ const LINE_BREAK_THRESHOLD = 50;
1818
* @param {import('./types.js').Options | undefined} options
1919
*/
2020
export function print(ast, options = undefined) {
21+
const comments = (ast.type === 'Root' && ast.comments) || [];
22+
2123
return esrap.print(
2224
ast,
2325
/** @type {Visitors<AST.SvelteNode>} */ ({
2426
...ts({
25-
comments: ast.type === 'Root' ? ast.comments : [],
27+
comments,
2628
getLeadingComments: options?.getLeadingComments,
2729
getTrailingComments: options?.getTrailingComments
2830
}),
29-
...svelte_visitors,
31+
...svelte_visitors(comments),
3032
...css_visitors
3133
})
3234
);
@@ -57,35 +59,72 @@ function block(context, node, allow_inline = false) {
5759
}
5860

5961
/**
62+
* @param {AST.BaseNode} node
6063
* @param {AST.BaseElement['attributes']} attributes
6164
* @param {Context} context
65+
* @param {AST.JSComment[]} comments
6266
* @returns {boolean} true if attributes were formatted on multiple lines
6367
*/
64-
function attributes(attributes, context) {
68+
function attributes(node, attributes, context, comments) {
6569
if (attributes.length === 0) {
6670
return false;
6771
}
6872

69-
// Measure total width of all attributes when rendered inline
70-
const child_context = context.new();
73+
let length = -1;
7174

72-
for (const attribute of attributes) {
73-
child_context.write(' ');
74-
child_context.visit(attribute);
75+
let comment_index = comments.findIndex((comment) => comment.start > node.start);
76+
77+
if (comment_index === -1) {
78+
comment_index = comments.length;
7579
}
7680

77-
const multiline = child_context.measure() > LINE_BREAK_THRESHOLD;
81+
const separator = context.new();
82+
83+
const children = attributes.map((attribute) => {
84+
const child_context = context.new();
85+
86+
while (comment_index < comments.length) {
87+
const comment = comments[comment_index];
88+
89+
if (comment.start < attribute.start) {
90+
if (comment.type === 'Line') {
91+
child_context.write('//' + comment.value);
92+
child_context.newline();
93+
} else {
94+
child_context.write('/*' + comment.value + '*/'); // TODO match indentation?
95+
child_context.append(separator);
96+
}
97+
98+
comment_index += 1;
99+
} else {
100+
break;
101+
}
102+
}
103+
104+
child_context.visit(attribute);
105+
106+
length += child_context.measure() + 1;
107+
108+
return child_context;
109+
});
110+
111+
let multiline = context.multiline || length > LINE_BREAK_THRESHOLD;
78112

79113
if (multiline) {
114+
separator.newline();
80115
context.indent();
81-
for (const attribute of attributes) {
116+
for (const child of children) {
82117
context.newline();
83-
context.visit(attribute);
118+
context.append(child);
84119
}
85120
context.dedent();
86121
context.newline();
87122
} else {
88-
context.append(child_context);
123+
separator.write(' ');
124+
for (const child of children) {
125+
context.write(' ');
126+
context.append(child);
127+
}
89128
}
90129

91130
return multiline;
@@ -94,8 +133,9 @@ function attributes(attributes, context) {
94133
/**
95134
* @param {AST.BaseElement} node
96135
* @param {Context} context
136+
* @param {AST.JSComment[]} comments
97137
*/
98-
function base_element(node, context) {
138+
function base_element(node, context, comments) {
99139
const child_context = context.new();
100140

101141
child_context.write('<' + node.name);
@@ -111,7 +151,7 @@ function base_element(node, context) {
111151
child_context.write('}');
112152
}
113153

114-
const multiline_attributes = attributes(node.attributes, child_context);
154+
const multiline_attributes = attributes(node, node.attributes, child_context, comments);
115155
const is_doctype_node = node.name.toLowerCase() === '!doctype';
116156
const is_self_closing =
117157
is_void(node.name) || (node.type === 'Component' && node.fragment.nodes.length === 0);
@@ -284,8 +324,11 @@ const css_visitors = {
284324
}
285325
};
286326

287-
/** @type {Visitors<AST.SvelteNode>} */
288-
const svelte_visitors = {
327+
/**
328+
* @param {AST.JSComment[]} comments
329+
* @returns {Visitors<AST.SvelteNode>}
330+
*/
331+
const svelte_visitors = (comments) => ({
289332
Root(node, context) {
290333
if (node.options) {
291334
context.write('<svelte:options');
@@ -315,7 +358,7 @@ const svelte_visitors = {
315358

316359
Script(node, context) {
317360
context.write('<script');
318-
attributes(node.attributes, context);
361+
attributes(node, node.attributes, context, comments);
319362
context.write('>');
320363
block(context, node.content);
321364
context.write('</script>');
@@ -545,7 +588,7 @@ const svelte_visitors = {
545588
},
546589

547590
Component(node, context) {
548-
base_element(node, context);
591+
base_element(node, context, comments);
549592
},
550593

551594
ConstTag(node, context) {
@@ -681,7 +724,7 @@ const svelte_visitors = {
681724
},
682725

683726
RegularElement(node, context) {
684-
base_element(node, context);
727+
base_element(node, context, comments);
685728
},
686729

687730
RenderTag(node, context) {
@@ -691,7 +734,7 @@ const svelte_visitors = {
691734
},
692735

693736
SlotElement(node, context) {
694-
base_element(node, context);
737+
base_element(node, context, comments);
695738
},
696739

697740
SnippetBlock(node, context) {
@@ -747,7 +790,7 @@ const svelte_visitors = {
747790

748791
StyleSheet(node, context) {
749792
context.write('<style');
750-
attributes(node.attributes, context);
793+
attributes(node, node.attributes, context, comments);
751794
context.write('>');
752795

753796
if (node.children.length > 0) {
@@ -774,7 +817,7 @@ const svelte_visitors = {
774817
},
775818

776819
SvelteBoundary(node, context) {
777-
base_element(node, context);
820+
base_element(node, context, comments);
778821
},
779822

780823
SvelteComponent(node, context) {
@@ -783,7 +826,7 @@ const svelte_visitors = {
783826
context.write(' this={');
784827
context.visit(node.expression);
785828
context.write('}');
786-
attributes(node.attributes, context);
829+
attributes(node, node.attributes, context, comments);
787830
if (node.fragment && node.fragment.nodes.length > 0) {
788831
context.write('>');
789832
block(context, node.fragment, true);
@@ -794,7 +837,7 @@ const svelte_visitors = {
794837
},
795838

796839
SvelteDocument(node, context) {
797-
base_element(node, context);
840+
base_element(node, context, comments);
798841
},
799842

800843
SvelteElement(node, context) {
@@ -803,7 +846,7 @@ const svelte_visitors = {
803846
context.write('this={');
804847
context.visit(node.tag);
805848
context.write('}');
806-
attributes(node.attributes, context);
849+
attributes(node, node.attributes, context, comments);
807850

808851
if (node.fragment && node.fragment.nodes.length > 0) {
809852
context.write('>');
@@ -815,27 +858,27 @@ const svelte_visitors = {
815858
},
816859

817860
SvelteFragment(node, context) {
818-
base_element(node, context);
861+
base_element(node, context, comments);
819862
},
820863

821864
SvelteHead(node, context) {
822-
base_element(node, context);
865+
base_element(node, context, comments);
823866
},
824867

825868
SvelteSelf(node, context) {
826-
base_element(node, context);
869+
base_element(node, context, comments);
827870
},
828871

829872
SvelteWindow(node, context) {
830-
base_element(node, context);
873+
base_element(node, context, comments);
831874
},
832875

833876
Text(node, context) {
834877
context.write(node.data);
835878
},
836879

837880
TitleElement(node, context) {
838-
base_element(node, context);
881+
base_element(node, context, comments);
839882
},
840883

841884
TransitionDirective(node, context) {
@@ -865,4 +908,4 @@ const svelte_visitors = {
865908
context.write('}');
866909
}
867910
}
868-
};
911+
});

0 commit comments

Comments
 (0)