diff --git a/CLAUDE.md b/CLAUDE.md index f1fba96a..a07283c3 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -132,8 +132,9 @@ One `SyntaxLang` lexer per language, registered via `add_lang`: (`xml`/`ssml`/`atom`/`rss`: plain tag scanning), one shared scanner parameterized by `MarkupLexMode` - `lexer_svelte.ts` - Svelte: the markup scanner in svelte mode (script→ts, - no special attrs) plus the `{…}` expression lexer (blocks, each/await - splits, at-directives, directive modifiers) + no special attrs, js-style comments between attributes) plus the `{…}` + expression lexer (blocks, each/await splits, at-directives, directive + modifiers, `{const …}`/`{let …}` declaration tags) - `lexer_md.ts` - Markdown: line-oriented block scan (fences with exact-word info matching and any-length closers, headings, blockquotes, lists, hr) with a per-block inline scan (emphasis, inline code, links, entities, raw diff --git a/src/lib/lexer_markup.ts b/src/lib/lexer_markup.ts index 47cbb1f0..8121eb8c 100644 --- a/src/lib/lexer_markup.ts +++ b/src/lib/lexer_markup.ts @@ -5,6 +5,7 @@ import { is_hex_digit, is_space, matches_ci, + scan_to_line_end, skip_space, token_type, type Lexer, @@ -91,6 +92,12 @@ export interface MarkupLexMode { * `style=`/`on*=` attribute embedding (html only). */ special_attrs: boolean; + /** + * JS-style line and block comments between attributes emit `comment` leaves + * (svelte only — in html/xml a `//` inside a tag is ordinary bogus attribute + * text, not a comment). + */ + attr_comments: boolean; /** * Lexes a `{…}` expression at `from`, returning the position after it — * the svelte hook, null for html/xml. `full` enables block/each forms @@ -108,6 +115,7 @@ export const MARKUP_MODE_HTML: MarkupLexMode = { script_container: T_LANG_JS, rcdata: true, special_attrs: true, + attr_comments: false, lex_expression: null, }; @@ -116,6 +124,7 @@ const MODE_XML: MarkupLexMode = { script_container: 0, rcdata: false, special_attrs: false, + attr_comments: false, lex_expression: null, }; @@ -366,6 +375,25 @@ const lex_markup_tag = ( tag_end = j + 2; break; } + if (mode.attr_comments && c === 47 && j + 1 < end) { + // js-style comment between attributes (svelte); a `//` line comment + // runs to the newline, a block comment to its closer, and either + // extends to the window end when unterminated (editor-style) + const c1 = text.charCodeAt(j + 1); + if (c1 === 47) { + const line_end = scan_to_line_end(text, j, end); + l.leaf(T_COMMENT, j, line_end); + j = line_end; + continue; + } + if (c1 === 42) { + const close = text.indexOf('*/', j + 2); + const comment_end = close === -1 || close + 2 > end ? end : close + 2; + l.leaf(T_COMMENT, j, comment_end); + j = comment_end; + continue; + } + } if (c === 47 || c === 61) { // stray `/` or `=` — plain text inside the tag j++; diff --git a/src/lib/lexer_svelte.ts b/src/lib/lexer_svelte.ts index afdfe825..3ed6f10a 100644 --- a/src/lib/lexer_svelte.ts +++ b/src/lib/lexer_svelte.ts @@ -15,12 +15,14 @@ import {lex_markup_window, type MarkupLexMode} from './lexer_markup.ts'; * in svelte mode plus the `{…}` expression lexer. * * Emits markup's tag/attr/comment/entity structure (no `special_attr` — - * svelte handles `style=`/`on*=` as ordinary attributes), `script`+`lang_ts` + * svelte handles `style=`/`on*=` as ordinary attributes; js-style line and + * block comments between attributes emit `comment` leaves), `script`+`lang_ts` * and `style`+`lang_css` regions (svelte scripts are always TypeScript), and * `svelte_expression` containers whose forms nest a same-span `block` - * (`{#if}`/`{:else if}`/`{/each}`/…), `each` (`{#each list as item}`), or - * `at_directive` (`{@render}`/`{@const}`/…) container with `special_keyword`/ - * `at_keyword`/`keyword` leaves and `lang_ts` interiors. + * (`{#if}`/`{:else if}`/`{/each}`/…), `each` (`{#each list as item}`), + * `at_directive` (`{@render}`/`{@const}`/…), or `declaration_tag` + * (`{const …}`/`{let …}`) container with `special_keyword`/`at_keyword`/ + * `keyword` leaves and `lang_ts` interiors. * * Notable behavior: expression braces balance to any depth and skip * strings/templates/comments (`{'}'}` works); expression-valued attributes @@ -38,6 +40,7 @@ import {lex_markup_window, type MarkupLexMode} from './lexer_markup.ts'; const T_SVELTE_EXPRESSION = token_type('svelte_expression'); const T_AT_DIRECTIVE = token_type('at_directive'); +const T_DECLARATION_TAG = token_type('declaration_tag'); const T_BLOCK = token_type('block'); const T_EACH = token_type('each'); const T_AT_KEYWORD = token_type('at_keyword'); @@ -105,10 +108,21 @@ const find_top_level_word = (text: string, from: number, to: number, word: strin return -1; }; +/** + * Length of a `const`/`let` declaration-tag keyword at `i` (with an identifier + * boundary after it), or 0 — the `{const …}`/`{let …}` recognizer. These are + * sigil-less declaration tags, the modern replacement for `{@const …}`. + */ +const declaration_keyword_len = (text: string, i: number, end: number): number => { + if (i + 5 <= end && text.startsWith('const', i) && !is_ident(text.charCodeAt(i + 5))) return 5; + if (i + 3 <= end && text.startsWith('let', i) && !is_ident(text.charCodeAt(i + 3))) return 3; + return 0; +}; + /** * Lexes a `{…}` expression at `from`, returning the position after it. - * `full` enables the block/each forms (top level); tag and attribute - * contexts pass false (at-directives like `{@attach …}` work in both). + * `full` enables the block/each and declaration-tag forms (top level); tag and + * attribute contexts pass false (at-directives like `{@attach …}` work in both). */ const lex_svelte_expression = (l: Lexer, from: number, end: number, full: boolean): number => { const {text} = l; @@ -119,6 +133,9 @@ const lex_svelte_expression = (l: Lexer, from: number, end: number, full: boolea l.open(T_SVELTE_EXPRESSION, from); const sigil = from + 1 < end ? text.charCodeAt(from + 1) : 0; + // `{const …}`/`{let …}` — only at top level; never collides with the + // `@`/`#`/`:`/`/` sigil forms since those first chars can't spell a keyword + const decl_len = full ? declaration_keyword_len(text, from + 1, inner_end) : 0; if (sigil === 64 && is_ident(text.charCodeAt(from + 2))) { // {@word …} let word_end = from + 2; @@ -178,6 +195,15 @@ const lex_svelte_expression = (l: Lexer, from: number, end: number, full: boolea if (closed) l.leaf(T_PUNCTUATION, close, expr_end); l.close(expr_end); } + } else if (decl_len !== 0) { + // {const …} / {let …} — the keyword leads, the rest is a ts interior + const kw_end = from + 1 + decl_len; + l.open(T_DECLARATION_TAG, from); + l.leaf(T_PUNCTUATION, from, from + 1); + l.leaf(T_KEYWORD, from + 1, kw_end); + lex_ts_interior(l, kw_end, inner_end); + if (closed) l.leaf(T_PUNCTUATION, close, expr_end); + l.close(expr_end); } else { lex_svelte_expression_plain(l, from, inner_end, close, expr_end, closed); } @@ -203,6 +229,7 @@ const SVELTE_MODE: MarkupLexMode = { script_container: T_LANG_TS, rcdata: false, special_attrs: false, + attr_comments: true, lex_expression: lex_svelte_expression, }; diff --git a/src/routes/samples/all.ts b/src/routes/samples/all.ts index 374722b8..f2994874 100644 --- a/src/routes/samples/all.ts +++ b/src/routes/samples/all.ts @@ -333,11 +333,20 @@ export const complex_regex = /^(?:\\/\\*.*?\\*\\/|\\/\\/.*|[^/])+$/; {#each thing_keys as [k, { t, u }] (f(k))} {@const v = Math.round(t[k + f(u)])} + {const doubled = v * 2} + {let label = \`\${k}: \${doubled}\`} {f(v)} + {label} {/each} {#if f(c)} - + {:else if f(a > 0)} bigger {:else} @@ -349,7 +358,7 @@ export const complex_regex = /^(?:\\/\\*.*?\\*\\/|\\/\\/.*|[^/])+$/; {@html 'raw html'} - + ... diff --git a/src/test/fixtures/generated/svelte/svelte_complex.html b/src/test/fixtures/generated/svelte/svelte_complex.html index 260d403f..07c09bf1 100644 --- a/src/test/fixtures/generated/svelte/svelte_complex.html +++ b/src/test/fixtures/generated/svelte/svelte_complex.html @@ -43,11 +43,20 @@ {#each thing_keys as [k, { t, u }] (f(k))} {@const v = Math.round(t[k + f(u)])} + {const doubled = v * 2} + {let label = `${k}: ${doubled}`} {f(v)} + {label} {/each} {#if f(c)} - <Thing string_prop="a {f('s')} b" number_prop={f(1)} /> + <Thing + string_prop="a {f('s')} b" + // interpolated string prop + /* number prop is + computed at runtime */ + number_prop={f(1)} + /> {:else if f(a > 0)} bigger {:else} @@ -59,7 +68,7 @@ <!-- eslint-disable-next-line svelte/no-at-html-tags --> {@html '<strong>raw html</strong>'} -<input bind:value type="text" class:active={c} /> +<input bind:value type="text" /* two-way bound */ class:active={c} /> <span {@attach attachment('param', f(42))}>...</span> diff --git a/src/test/fixtures/generated/svelte/svelte_complex.txt b/src/test/fixtures/generated/svelte/svelte_complex.txt index 8f062ad0..11b2215d 100644 --- a/src/test/fixtures/generated/svelte/svelte_complex.txt +++ b/src/test/fixtures/generated/svelte/svelte_complex.txt @@ -1,33 +1,37 @@ === STATS === -Sample length: 2348 characters -Total tokens: 647 +Sample length: 2517 characters +Total tokens: 680 -Token types (34 unique): - punctuation: 297 +Token types (39 unique): + punctuation: 303 tag: 78 - operator: 37 - lang_ts: 25 - svelte_expression: 25 + operator: 40 + lang_ts: 28 + svelte_expression: 28 function: 24 attr_name: 19 attr_value: 16 special_keyword: 15 - keyword: 13 - string: 11 + keyword: 15 + string: 12 + comment: 10 builtin: 10 capitalized_identifier: 8 property: 8 - comment: 7 block: 7 selector: 7 + number: 6 at_directive: 6 at_keyword: 6 - number: 5 + interpolation_punctuation: 4 script: 3 namespace: 3 constant: 2 boolean: 2 function_variable: 2 + declaration_tag: 2 + template_punctuation: 2 + interpolation: 2 style: 2 lang_css: 2 import_type_keyword: 1 @@ -35,6 +39,7 @@ Token types (34 unique): :: 1 type: 1 each: 1 + template_string: 1 atrule: 1 rule: 1 @@ -255,434 +260,467 @@ Token types (34 unique): 849-850 punctuation ( 851-854 punctuation )]) 854-855 punctuation } - 857-863 svelte_expression {f(v)} + 857-880 svelte_expression {const doubled = v * 2} + 857-880 declaration_tag {const doubled = v * 2} 857-858 punctuation { - 858-862 lang_ts f(v) - 858-859 function f - 859-860 punctuation ( - 861-862 punctuation ) - 862-863 punctuation } - 864-871 svelte_expression {/each} - 864-871 block {/each} - 864-865 punctuation { - 865-870 special_keyword /each - 870-871 punctuation } - 873-883 svelte_expression {#if f(c)} - 873-883 block {#if f(c)} - 873-874 punctuation { - 874-877 special_keyword #if - 878-882 lang_ts f(c) - 878-879 function f - 879-880 punctuation ( - 881-882 punctuation ) - 882-883 punctuation } - 885-940 tag - 885-891 tag - 941-960 svelte_expression {:else if f(a > 0)} - 941-960 block {:else if f(a > 0)} + 858-863 keyword const + 864-879 lang_ts doubled = v * 2 + 872-873 operator = + 876-877 operator * + 878-879 number 2 + 879-880 punctuation } + 882-914 svelte_expression {let label = `${k}: ${doubled}`} + 882-914 declaration_tag {let label = `${k}: ${doubled}`} + 882-883 punctuation { + 883-886 keyword let + 887-913 lang_ts label = `${k}: ${doubled}` + 893-894 operator = + 895-913 template_string `${k}: ${doubled}` + 895-896 template_punctuation ` + 896-900 interpolation ${k} + 896-898 interpolation_punctuation ${ + 899-900 interpolation_punctuation } + 900-902 string : + 902-912 interpolation ${doubled} + 902-904 interpolation_punctuation ${ + 911-912 interpolation_punctuation } + 912-913 template_punctuation ` + 913-914 punctuation } + 916-922 svelte_expression {f(v)} + 916-917 punctuation { + 917-921 lang_ts f(v) + 917-918 function f + 918-919 punctuation ( + 920-921 punctuation ) + 921-922 punctuation } + 924-931 svelte_expression {label} + 924-925 punctuation { + 925-930 lang_ts label + 930-931 punctuation } + 932-939 svelte_expression {/each} + 932-939 block {/each} + 932-933 punctuation { + 933-938 special_keyword /each + 938-939 punctuation } + 941-951 svelte_expression {#if f(c)} + 941-951 block {#if f(c)} 941-942 punctuation { - 942-950 special_keyword :else if - 951-959 lang_ts f(a > 0) - 951-952 function f - 952-953 punctuation ( - 955-956 operator > - 957-958 number 0 - 958-959 punctuation ) - 959-960 punctuation } - 969-976 svelte_expression {:else} - 969-976 block {:else} - 969-970 punctuation { - 970-975 special_keyword :else - 975-976 punctuation } - 978-1029 tag (c = f(!c))}> - 978-984 tag (c = f(!c))} -1008-1009 punctuation = -1009-1028 svelte_expression {() => (c = f(!c))} -1009-1010 punctuation { -1010-1027 lang_ts () => (c = f(!c)) -1010-1012 punctuation () -1013-1015 operator => -1016-1017 punctuation ( -1019-1020 operator = -1021-1022 function f -1022-1023 punctuation ( -1023-1024 operator ! -1025-1027 punctuation )) -1027-1028 punctuation } -1028-1029 punctuation > -1032-1052 svelte_expression {@render children()} -1032-1052 at_directive {@render children()} -1032-1033 punctuation { -1033-1040 at_keyword @render -1041-1051 lang_ts children() -1041-1049 function children -1049-1051 punctuation () -1051-1052 punctuation } -1054-1062 tag -1054-1061 tag -1063-1068 svelte_expression {/if} -1063-1068 block {/if} -1063-1064 punctuation { -1064-1067 special_keyword /if -1067-1068 punctuation } -1070-1126 comment -1127-1162 svelte_expression {@html 'raw html'} -1127-1162 at_directive {@html 'raw html'} -1127-1128 punctuation { -1128-1133 at_keyword @html -1134-1161 lang_ts 'raw html' -1134-1161 string 'raw html' -1161-1162 punctuation } -1164-1213 tag -1164-1170 tag -1215-1258 tag -1215-1220 tag -1261-1268 tag -1261-1267 tag -1270-1326 tag
{\n\t// function attachment arg\n})}> -1270-1274 tag
{\n\t// function attachment arg\n})} -1275-1325 at_directive {@attach f(() => {\n\t// function attachment arg\n})} -1275-1276 punctuation { -1276-1283 at_keyword @attach -1284-1324 lang_ts f(() => {\n\t// function attachment arg\n}) -1284-1285 function f -1285-1288 punctuation (() -1289-1291 operator => -1292-1293 punctuation { -1295-1321 comment // function attachment arg -1322-1324 punctuation }) -1324-1325 punctuation } -1325-1326 punctuation > -1336-1342 tag
-1336-1341 tag
-1344-1369 svelte_expression {@render my_snippet('p')} -1344-1369 at_directive {@render my_snippet('p')} -1344-1345 punctuation { -1345-1352 at_keyword @render -1353-1368 lang_ts my_snippet('p') -1353-1363 function my_snippet -1363-1364 punctuation ( -1364-1367 string 'p' -1367-1368 punctuation ) -1368-1369 punctuation } -1371-1403 svelte_expression {#snippet my_snippet(p: string)} -1371-1403 block {#snippet my_snippet(p: string)} -1371-1372 punctuation { -1372-1380 special_keyword #snippet -1381-1402 lang_ts my_snippet(p: string) -1381-1391 function my_snippet -1391-1392 punctuation ( -1393-1394 operator : -1395-1401 builtin string -1401-1402 punctuation ) -1402-1403 punctuation } -1405-1437 tag -1440-1448 tag -1450-1460 svelte_expression {/snippet} -1450-1460 block {/snippet} -1450-1451 punctuation { -1451-1459 special_keyword /snippet -1459-1460 punctuation } -1462-1511 tag

-1462-1464 tag

{\n\t// function attachment arg\n})}> +1439-1443 tag

{\n\t// function attachment arg\n})} +1444-1494 at_directive {@attach f(() => {\n\t// function attachment arg\n})} +1444-1445 punctuation { +1445-1452 at_keyword @attach +1453-1493 lang_ts f(() => {\n\t// function attachment arg\n}) +1453-1454 function f +1454-1457 punctuation (() +1458-1460 operator => +1461-1462 punctuation { +1464-1490 comment // function attachment arg +1491-1493 punctuation }) +1493-1494 punctuation } +1494-1495 punctuation > +1505-1511 tag
+1505-1510 tag -1518-1538 tag -1518-1523 tag -1542-1549 tag -1542-1548 tag -1550-1554 tag

-1550-1553 tag

-1556-1587 tag -1597-1605 tag +1574-1581 tag +1619-1629 svelte_expression {/snippet} +1619-1629 block {/snippet} +1619-1620 punctuation { +1620-1628 special_keyword /snippet +1628-1629 punctuation } +1631-1680 tag

+1631-1633 tag

+1687-1707 tag +1687-1692 tag -1725-1729 tag

    -1725-1728 tag
      +1711-1718 tag +1711-1717 tag +1719-1723 tag

      +1719-1722 tag

      +1725-1756 tag
    -1774-1778 tag
-1781-1824 comment -1825-1830 tag
-1825-1829 tag
-1832-1840 tag -1878-1886 tag -1889-1896 tag -1930-1937 tag -1939-1945 tag
-1939-1944 tag
-1947-1954 tag -2339-2346 tag +1733-1737 attr_name type +1737-1746 attr_value ="button" +1737-1738 punctuation = +1738-1739 punctuation " +1745-1746 punctuation " +1747-1755 attr_name disabled +1755-1756 punctuation > +1766-1775 tag +1766-1774 tag +1777-1816 comment +1817-1830 svelte_expression {b.repeat(2)} +1817-1818 punctuation { +1818-1829 lang_ts b.repeat(2) +1819-1820 punctuation . +1820-1826 function repeat +1826-1827 punctuation ( +1827-1828 number 2 +1828-1829 punctuation ) +1829-1830 punctuation } +1831-1838 svelte_expression {bound} +1831-1832 punctuation { +1832-1837 lang_ts bound +1837-1838 punctuation } +1840-1846 tag
+1840-1843 tag
+1848-1854 tag
+1848-1851 tag
+1856-1892 tag access +1856-1860 tag +1894-1898 tag
    +1894-1897 tag
      +1900-1904 tag
    • +1900-1903 tag
    • +1915-1920 tag
    • +1915-1919 tag +1922-1926 tag
    • +1922-1925 tag
    • +1937-1942 tag
    • +1937-1941 tag +1943-1948 tag
    +1943-1947 tag
+1950-1993 comment +1994-1999 tag
+1994-1998 tag
+2001-2009 tag +2047-2055 tag +2058-2065 tag +2099-2106 tag +2108-2114 tag
+2108-2113 tag
+2116-2123 tag +2508-2515 tag diff --git a/src/test/fixtures/samples/sample_complex.svelte b/src/test/fixtures/samples/sample_complex.svelte index 517e3d94..3dc6131c 100644 --- a/src/test/fixtures/samples/sample_complex.svelte +++ b/src/test/fixtures/samples/sample_complex.svelte @@ -43,11 +43,20 @@ {#each thing_keys as [k, { t, u }] (f(k))} {@const v = Math.round(t[k + f(u)])} + {const doubled = v * 2} + {let label = `${k}: ${doubled}`} {f(v)} + {label} {/each} {#if f(c)} - + {:else if f(a > 0)} bigger {:else} @@ -59,7 +68,7 @@ {@html 'raw html'} - + ... diff --git a/src/test/lexer_markup.test.ts b/src/test/lexer_markup.test.ts index ab622a7e..bceda3de 100644 --- a/src/test/lexer_markup.test.ts +++ b/src/test/lexer_markup.test.ts @@ -91,6 +91,14 @@ describe('lexer_markup tags', () => { assert.deepEqual(tokens[0], ['tag', text]); assert.deepEqual(validate_syntax_events(syntax_styler_global.lex(text, 'html')), []); }); + + test('js-style comments in tags stay bogus attributes (svelte-only feature)', () => { + // `//` and `/* */` between attributes are comments in svelte, but in + // html/xml they are ordinary bogus attribute text + assert.deepEqual(picked('
', ['comment']), []); + assert.deepEqual(picked('
', ['attr_name']), [['attr_name', 'x']]); + assert.deepEqual(picked('
', ['comment'], 'xml'), []); + }); }); describe('lexer_markup entities', () => { diff --git a/src/test/lexer_svelte.test.ts b/src/test/lexer_svelte.test.ts index 17f3d5ff..f5facb31 100644 --- a/src/test/lexer_svelte.test.ts +++ b/src/test/lexer_svelte.test.ts @@ -164,6 +164,89 @@ describe('lexer_svelte at-directives', () => { }); }); +describe('lexer_svelte declaration tags', () => { + test('`{const …}` and `{let …}` wrap a declaration_tag with a keyword lead', () => { + assert.deepEqual(tokens_of('{const area = w * h}').slice(0, 5), [ + ['svelte_expression', '{const area = w * h}'], + ['declaration_tag', '{const area = w * h}'], + ['punctuation', '{'], + ['keyword', 'const'], + ['lang_ts', 'area = w * h'], + ]); + assert.deepEqual(picked('{let name = user.name}', ['declaration_tag', 'keyword']), [ + ['declaration_tag', '{let name = user.name}'], + ['keyword', 'let'], + ]); + }); + + test('a `const`/`let`-prefixed identifier is not a declaration tag', () => { + assert.deepEqual(picked('{const_value + 1}', ['declaration_tag']), []); + assert.deepEqual(tokens_of('{const_value + 1}')[0], ['svelte_expression', '{const_value + 1}']); + }); + + test('declaration tags are top-level only, not in attribute position', () => { + // `full` is false in tag/attribute context — the leading `const` stays a + // plain ts keyword with no declaration_tag container + assert.deepEqual(picked('', ['declaration_tag']), []); + assert.deepEqual(picked('', ['keyword']), [['keyword', 'const']]); + }); + + test('an unterminated declaration tag stays valid', () => { + const text = '{const x ='; + assert.deepEqual(validate_syntax_events(syntax_styler_global.lex(text, 'svelte')), []); + assert.deepEqual(picked(text, ['declaration_tag', 'keyword']), [ + ['declaration_tag', '{const x ='], + ['keyword', 'const'], + ]); + }); + + test('the legacy `{@const}` stays an at_directive, not a declaration tag', () => { + assert.deepEqual(picked('{@const x = 1}', ['at_directive', 'declaration_tag', 'at_keyword']), [ + ['at_directive', '{@const x = 1}'], + ['at_keyword', '@const'], + ]); + }); +}); + +describe('lexer_svelte attribute comments', () => { + test('line and block comments between attributes emit comment leaves', () => { + assert.deepEqual(picked('
', ['comment', 'attr_name']), [ + ['attr_name', 'a'], + ['comment', '// note'], + ['comment', '/* block */'], + ['attr_name', 'b'], + ]); + }); + + test('multiple inline block comments, and an unterminated one extends to the end', () => { + assert.deepEqual(picked('', ['comment']), [ + ['comment', '/* a */'], + ['comment', '/* b */'], + ]); + const text = '
`', () => { + // js line-comment semantics — the tag closes on the next line + assert.deepEqual(picked('
\n>', ['comment', 'punctuation']), [ + ['punctuation', '<'], + ['comment', '// c >'], + ['punctuation', '>'], + ]); + }); + + test('a block comment is opaque — an inner `>` does not close the tag', () => { + const text = '
b */ c="1">'; + assert.deepEqual(validate_syntax_events(syntax_styler_global.lex(text, 'svelte')), []); + assert.deepEqual(picked(text, ['comment', 'attr_name']), [ + ['comment', '/* a > b */'], + ['attr_name', 'c'], + ]); + }); +}); + describe('lexer_svelte tags', () => { test('an expression-valued attribute', () => { assert.deepEqual(tokens_of('').slice(3, 9), [ @@ -293,7 +376,15 @@ describe('lexer_svelte sample', () => { const types = new Set( syntax_events_to_tokens(syntax_styler_global.lex(content, 'svelte')).map((t) => t.type), ); - for (const t of ['tag', 'svelte_expression', 'lang_ts', 'attr_name', 'block', 'at_directive']) { + for (const t of [ + 'tag', + 'svelte_expression', + 'lang_ts', + 'attr_name', + 'block', + 'at_directive', + 'declaration_tag', + ]) { assert.ok(types.has(t), `expected a ${t} token in the sample`); } });