diff --git a/.changeset/olive-jobs-sip.md b/.changeset/olive-jobs-sip.md
new file mode 100644
index 000000000..8047f8436
--- /dev/null
+++ b/.changeset/olive-jobs-sip.md
@@ -0,0 +1,5 @@
+---
+"svelte-language-server": patch
+---
+
+feat: support for comments in tags
diff --git a/packages/language-server/package.json b/packages/language-server/package.json
index 23cb6aa51..29743d592 100644
--- a/packages/language-server/package.json
+++ b/packages/language-server/package.json
@@ -59,7 +59,7 @@
"globrex": "^0.1.2",
"lodash": "^4.17.21",
"prettier": "~3.3.3",
- "prettier-plugin-svelte": "^3.4.0",
+ "prettier-plugin-svelte": "^3.5.0",
"svelte": "^4.2.19",
"svelte2tsx": "workspace:~",
"typescript": "^5.9.2",
diff --git a/packages/language-server/src/lib/documents/parseHtml.ts b/packages/language-server/src/lib/documents/parseHtml.ts
index afe239e2d..44d1cdc63 100644
--- a/packages/language-server/src/lib/documents/parseHtml.ts
+++ b/packages/language-server/src/lib/documents/parseHtml.ts
@@ -137,6 +137,27 @@ export function parseHtml(text: string): HTMLDocument {
parseAttributeValue();
break;
+ case TokenType.Unknown: {
+ const tokenOffset = scanner.getTokenOffset();
+ if (
+ isInsideTagScannerState() &&
+ text.charCodeAt(tokenOffset) === '/'.charCodeAt(0)
+ ) {
+ const nextCharCode = text.charCodeAt(tokenOffset + 1);
+ if (nextCharCode === '/'.charCodeAt(0)) {
+ const newlineOffset = text.indexOf('\n', tokenOffset + 2);
+ const commentEndOffset = newlineOffset === -1 ? text.length : newlineOffset;
+ restartScannerAt(commentEndOffset, ScannerState.WithinTag);
+ } else if (nextCharCode === '*'.charCodeAt(0)) {
+ const blockCommentEnd = text.indexOf('*/', tokenOffset + 2);
+ const commentEndOffset =
+ blockCommentEnd === -1 ? text.length : blockCommentEnd + 2;
+ restartScannerAt(commentEndOffset, ScannerState.WithinTag);
+ }
+ }
+ break;
+ }
+
case TokenType.Content: {
const expressionEnd = skipExpressionInCurrentRange();
if (expressionEnd > scanner.getTokenEnd()) {
@@ -223,6 +244,16 @@ export function parseHtml(text: string): HTMLDocument {
}
finishAttribute(start, expressionTagEnd);
}
+
+ function isInsideTagScannerState() {
+ const scannerState = scanner.getScannerState();
+ return (
+ scannerState === ScannerState.WithinTag ||
+ scannerState === ScannerState.AfterAttributeName ||
+ scannerState === ScannerState.BeforeAttributeValue ||
+ scannerState === ScannerState.AfterOpeningStartTag
+ );
+ }
}
export interface AttributeContext {
diff --git a/packages/language-server/test/lib/documents/parseHtml.test.ts b/packages/language-server/test/lib/documents/parseHtml.test.ts
index 8349a4066..e5fb6024f 100644
--- a/packages/language-server/test/lib/documents/parseHtml.test.ts
+++ b/packages/language-server/test/lib/documents/parseHtml.test.ts
@@ -198,6 +198,20 @@ describe('parseHtml', () => {
const ariaLabelValue = fooNode.attributes?.['ariaLabel'];
assert.strictEqual(ariaLabelValue, `"a{b > c ? "": ""} c"`);
});
+
+ it('parse comments in attributes', () => {
+ testRootElements(
+ parseHtml(
+ `