Skip to content

Commit f12de44

Browse files
committed
feat: support for comments in tags
see sveltejs/svelte#17671
1 parent 9b7025a commit f12de44

File tree

7 files changed

+85
-6
lines changed

7 files changed

+85
-6
lines changed

packages/language-server/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@
5959
"globrex": "^0.1.2",
6060
"lodash": "^4.17.21",
6161
"prettier": "~3.3.3",
62-
"prettier-plugin-svelte": "^3.4.0",
62+
"prettier-plugin-svelte": "^3.5.0",
6363
"svelte": "^4.2.19",
6464
"svelte2tsx": "workspace:~",
6565
"typescript": "^5.9.2",

packages/language-server/src/lib/documents/parseHtml.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,27 @@ export function parseHtml(text: string): HTMLDocument {
137137
parseAttributeValue();
138138
break;
139139

140+
case TokenType.Unknown: {
141+
const tokenOffset = scanner.getTokenOffset();
142+
if (
143+
isInsideTagScannerState() &&
144+
text.charCodeAt(tokenOffset) === '/'.charCodeAt(0)
145+
) {
146+
const nextCharCode = text.charCodeAt(tokenOffset + 1);
147+
if (nextCharCode === '/'.charCodeAt(0)) {
148+
const newlineOffset = text.indexOf('\n', tokenOffset + 2);
149+
const commentEndOffset = newlineOffset === -1 ? text.length : newlineOffset;
150+
restartScannerAt(commentEndOffset, ScannerState.WithinTag);
151+
} else if (nextCharCode === '*'.charCodeAt(0)) {
152+
const blockCommentEnd = text.indexOf('*/', tokenOffset + 2);
153+
const commentEndOffset =
154+
blockCommentEnd === -1 ? text.length : blockCommentEnd + 2;
155+
restartScannerAt(commentEndOffset, ScannerState.WithinTag);
156+
}
157+
}
158+
break;
159+
}
160+
140161
case TokenType.Content: {
141162
const expressionEnd = skipExpressionInCurrentRange();
142163
if (expressionEnd > scanner.getTokenEnd()) {
@@ -223,6 +244,16 @@ export function parseHtml(text: string): HTMLDocument {
223244
}
224245
finishAttribute(start, expressionTagEnd);
225246
}
247+
248+
function isInsideTagScannerState() {
249+
const scannerState = scanner.getScannerState();
250+
return (
251+
scannerState === ScannerState.WithinTag ||
252+
scannerState === ScannerState.AfterAttributeName ||
253+
scannerState === ScannerState.BeforeAttributeValue ||
254+
scannerState === ScannerState.AfterOpeningStartTag
255+
);
256+
}
226257
}
227258

228259
export interface AttributeContext {

packages/language-server/test/lib/documents/parseHtml.test.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,20 @@ describe('parseHtml', () => {
198198
const ariaLabelValue = fooNode.attributes?.['ariaLabel'];
199199
assert.strictEqual(ariaLabelValue, `"a{b > c ? "": ""} c"`);
200200
});
201+
202+
it('parse comments in attributes', () => {
203+
testRootElements(
204+
parseHtml(
205+
`<Foo
206+
// comment/>
207+
checked={a}
208+
/* another comment/> <div>ignore me</div> */
209+
hello="bar"
210+
/>
211+
<style></style>`
212+
)
213+
);
214+
});
201215
});
202216

203217
describe('getAttributeContextAtPosition', () => {

packages/svelte-vscode/syntaxes/svelte.tmLanguage.src.yaml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -391,11 +391,23 @@ repository:
391391

392392
attributes:
393393
patterns:
394+
- include: '#attributes-comments'
394395
- include: '#attributes-directives'
395396
- include: '#attributes-keyvalue'
396397
- include: '#attributes-attach'
397398
- include: '#attributes-interpolated'
398399

400+
# Comments within attributes
401+
attributes-comments:
402+
patterns:
403+
# Single-line comment
404+
- match: //.*$
405+
name: comment.line.double-slash.svelte
406+
# Block comment
407+
- begin: /\*
408+
end: \*/
409+
name: comment.block.svelte
410+
399411
# Attachments
400412
attributes-attach:
401413
begin: (?<!:|=)\s*({@attach\s)
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
///<reference types="svelte" />
2+
;function $$render() {
3+
async () => { { svelteHTML.createElement("div", { "foo":`bar`,"baz":`qux`,}); }
4+
5+
{ const $$_tnenopmoC0C = __sveltets_2_ensureComponent(Component); new $$_tnenopmoC0C({ target: __sveltets_2_any(), props: { "foo":`bar`,"baz":`qux`,}}); Component}};
6+
return { props: /** @type {Record<string, never>} */ ({}), exports: {}, bindings: "", slots: {}, events: {} }}
7+
const Input__SvelteComponent_ = __sveltets_2_isomorphic_component(__sveltets_2_partial(__sveltets_2_with_any_event($$render())));
8+
/*Ωignore_startΩ*/type Input__SvelteComponent_ = InstanceType<typeof Input__SvelteComponent_>;
9+
/*Ωignore_endΩ*/export default Input__SvelteComponent_;
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<div
2+
// comment
3+
foo="bar"
4+
/* another comment */
5+
baz="qux"
6+
></div>
7+
8+
<Component
9+
// comment
10+
foo="bar"
11+
/* another comment */
12+
baz="qux"
13+
></Component>

pnpm-lock.yaml

Lines changed: 5 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)