Skip to content

Commit 65c8a14

Browse files
committed
Merge branch 'claude/jovial-snyder-3d4f82'
2 parents eee32a7 + 2a16adc commit 65c8a14

7 files changed

Lines changed: 19 additions & 13 deletions

File tree

html.tmLanguage.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1040,7 +1040,7 @@
10401040
"attribute": {
10411041
"patterns": [
10421042
{
1043-
"begin": "(on\\w+)(?![\\w:.-])",
1043+
"begin": "(on[A-Za-z0-9_]+)(?![\\w:.-])",
10441044
"beginCaptures": {
10451045
"1": {
10461046
"name": "entity.other.attribute-name.html"

html.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,12 @@
99
// tags). It does not implement the WHATWG error-recovery tree-construction
1010
// algorithm (that is not a context-free grammar); conformance is measured against
1111
// `parse5` on well-formed input. See memory: html-vue-markup.
12-
import { token, rule, defineGrammar, many, opt, alt, seq, oneOf, noneOf, range, anyChar, star, plus, notFollowedBy } from './src/api.ts';
12+
import { token, rule, defineGrammar, many, opt, alt, lit, seq, oneOf, noneOf, range, anyChar, star, plus, notFollowedBy } from './src/api.ts';
1313
import type { MarkupConfig } from './src/types.ts';
1414

1515
// ── Tokens ──
16+
// ASCII word chars [A-Za-z0-9_] — NOT `\w` (Oniguruma treats `\w` as Unicode-aware); tag/attr
17+
// names and the `on*` embed selector are intentionally ASCII, so the IR enumerates the set.
1618
const word = oneOf(range('A', 'Z'), range('a', 'z'), range('0', '9'), '_');
1719
const whitespace = oneOf('\t', '\n', '\f', '\r', ' ');
1820

@@ -120,8 +122,8 @@ export const markup: MarkupConfig = {
120122
// VS Code's own HTML grammar (a flat source.css blob there) and matching the hand-written Vue
121123
// grammar's `#vue-directives-style-attr` (its "Copy from source.css#rule-list-innards").
122124
attributeEmbed: [
123-
{ namePattern: 'on\\w+', embed: 'source.js' },
124-
{ namePattern: 'style', embed: 'source.css', include: 'source.css#rule-list-innards' },
125+
{ namePattern: seq(lit('on'), plus(word)), embed: 'source.js' },
126+
{ namePattern: lit('style'), embed: 'source.css', include: 'source.css#rule-list-innards' },
125127
],
126128
// Raw-text element bodies are scanned verbatim by the parser, but the HIGHLIGHTER
127129
// delegates `<script>`/`<style>` to the platform's real JS/CSS grammars (exactly as

src/gen-tm.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
tokenPatternSource,
1717
tokenPatternStartsWithDecimal,
1818
tokenPatternStringDelimiters,
19+
tokenPatternToRegex,
1920
tokenPatternTrailingCharClass,
2021
} from './token-pattern.ts';
2122

@@ -3863,7 +3864,7 @@ function generateMarkupTm(grammar: CstGrammar, grammarName: string, scopeName: s
38633864
// name/value tie on a real `on*`; leftmost-match still prefers the generic name rule for
38643865
// `data-on…` (it matches at the earlier, true attribute start). `(?![\w:.-])` completes the name.
38653866
const attrEmbed: TmPattern[] = (m.attributeEmbed ?? []).map(spec => ({
3866-
begin: `(${spec.namePattern})(?![\\w:.-])`,
3867+
begin: `(${tokenPatternToRegex(spec.namePattern)})(?![\\w:.-])`,
38673868
beginCaptures: { '1': { name: sAttr } },
38683869
end: `(?=[\\s${escapeForCharClass(m.tagClose)}${escapeForCharClass(m.closeMarker ?? '/')}])`,
38693870
patterns: embedValuePatterns(spec.embed, spec.include ?? spec.embed, sEq, assign, attrQuotes, quoteCc, { begin: sStrPunctB, end: sStrPunctE }, spec.valuePatterns),
@@ -3986,7 +3987,7 @@ function buildMarkupInjectParts(grammar: CstGrammar, mainScopeName: string): { r
39863987
const dir: TmPattern[] = [];
39873988
for (const c of d.control) { // v-for / v-if … — distinct scope, value embedded
39883989
dir.push({
3989-
begin: `${beforeAttr}(${c.match})(?=[${assignCc}\\s${ccSlash}${ccClose}]|$)`,
3990+
begin: `${beforeAttr}(${tokenPatternToRegex(c.match)})(?=[${assignCc}\\s${ccSlash}${ccClose}]|$)`,
39903991
beginCaptures: { '1': { name: c.scope } },
39913992
end: endAttr, patterns: values,
39923993
});

src/token-pattern.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,9 @@ function quantifier(min: number, max: number | undefined): string {
263263
return `{${min},${max}}`;
264264
}
265265

266+
// A char class serializes to an explicit `[…]`, never a `\w`/`\d`/`\s` shorthand: Oniguruma's
267+
// shorthands are Unicode-aware (`\d` matches U+FF10, `\w` matches `é`/`你`), so condensing an
268+
// enumerated ASCII set to a shorthand would silently widen the meaning the IR declares.
266269
function emitCharClass(pattern: Extract<TokenPattern, { type: 'charClass' }>): string {
267270
return `[${pattern.negate ? '^' : ''}${pattern.items.map(emitClassItem).join('')}]`;
268271
}

src/types.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ export interface MarkupConfig {
141141
// {name,match}); when present it REPLACES `include` inside the value span (the quote bounding is
142142
// unchanged). Lets an embed mirror a hand-written grammar's value rule verbatim (e.g. Volar's
143143
// `vue-directives-generic-attr`) using the host's PUBLIC repository keys.
144-
attributeEmbed?: { namePattern: string; embed: string; include?: string; valuePatterns?: RepoAlias[] }[];
144+
attributeEmbed?: { namePattern: TokenPattern; embed: string; include?: string; valuePatterns?: RepoAlias[] }[];
145145
// Elements whose content is raw (CDATA-like): after the start tag's `tagClose`,
146146
// everything up to the matching `tagOpen+closeMarker+name` is one `token`. `embed`
147147
// optionally maps a tag → the grammar scope to embed in its body (e.g. Vue SFC blocks:
@@ -236,7 +236,7 @@ export interface MarkupInject {
236236
scopeName: string; // emitted file's scopeName, e.g. vue.directives
237237
repoKey: string; // main-grammar repository key the stub includes, e.g. vue-directives
238238
selector: InjectClause[]; // host scopes (e.g. meta.tag / meta.element)
239-
control: { match: string; scope: string }[]; // e.g. [{match:'v-for', scope:'keyword.control.loop.vue'}, …]
239+
control: { match: TokenPattern; scope: string }[]; // e.g. [{match:lit('v-for'), scope:'keyword.control.loop.vue'}, …]
240240
shorthand: { char: string; scope: string }[]; // e.g. [{char:':', scope:'punctuation.attribute-shorthand.bind.html.vue'}, …]
241241
prefix: string; // long-form directive prefix, e.g. 'v-'
242242
nameScope: string; // scope for a directive name / argument (entity.other.attribute-name.html.vue)

vue.tmLanguage.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3286,7 +3286,7 @@
32863286
"attribute": {
32873287
"patterns": [
32883288
{
3289-
"begin": "(on\\w+)(?![\\w:.-])",
3289+
"begin": "(on[A-Za-z0-9_]+)(?![\\w:.-])",
32903290
"beginCaptures": {
32913291
"1": {
32923292
"name": "entity.other.attribute-name.vue"

vue.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
// is HTML's raw-text mechanism with a per-block embed map) and only swaps the markup config
1515
// + scope name. We import those reusable pieces and build through `defineGrammar` — the same
1616
// API every other grammar uses — instead of spreading html's already-built grammar object.
17-
import { defineGrammar } from './src/api.ts';
17+
import { defineGrammar, lit, alt } from './src/api.ts';
1818
import { tokens, rules, scopes, markup as htmlMarkup } from './html.ts';
1919

2020
export default defineGrammar({
@@ -52,7 +52,7 @@ export default defineGrammar({
5252
// SAME vue grammar runs on either host (proven by test/vue-dropin.ts). Vue-only, so it extends
5353
// html.ts's `on*`/`style` embeds rather than polluting the HTML grammar.
5454
attributeEmbed: [...(htmlMarkup.attributeEmbed ?? []), {
55-
namePattern: 'generic', embed: 'source.ts',
55+
namePattern: lit('generic'), embed: 'source.ts',
5656
valuePatterns: [
5757
{ include: 'source.ts#comment' },
5858
{ name: 'storage.modifier.ts', match: '(?<![_$[:alnum:]])(?:(?<=\\.\\.\\.)|(?<!\\.))(extends|in|out)(?![_$[:alnum:]])(?:(?=\\.\\.\\.)|(?!\\.))' },
@@ -127,8 +127,8 @@ export default defineGrammar({
127127
{ scope: 'meta.element', excludes: ['meta.attribute'] },
128128
],
129129
control: [
130-
{ match: 'v-for', scope: 'keyword.control.loop.vue' },
131-
{ match: 'v-if|v-else-if|v-else', scope: 'keyword.control.conditional.vue' },
130+
{ match: lit('v-for'), scope: 'keyword.control.loop.vue' },
131+
{ match: alt(lit('v-if'), lit('v-else-if'), lit('v-else')), scope: 'keyword.control.conditional.vue' },
132132
],
133133
shorthand: [
134134
{ char: ':', scope: 'punctuation.attribute-shorthand.bind.html.vue' },

0 commit comments

Comments
 (0)