Skip to content

Commit 4a26317

Browse files
Add RedCMD TextMate diagnostics guard (#13)
1 parent f13a5bc commit 4a26317

14 files changed

Lines changed: 535 additions & 65 deletions

.github/workflows/ci.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ jobs:
1313
runs-on: ubuntu-latest
1414
steps:
1515
- uses: actions/checkout@v4
16+
with:
17+
submodules: true
1618

1719
# Node 24+ runs the .ts sources directly (native type stripping) — no build, no tsx.
1820
- uses: actions/setup-node@v4
@@ -44,6 +46,7 @@ jobs:
4446
node test/js-conformance.ts
4547
node test/tsx-conformance.ts
4648
node test/jsx-conformance.ts
49+
node test/redcmd-tm-diagnostics.ts
4750
node test/html-lexer-spike.ts
4851
node test/html-conformance.ts
4952
node test/html-monarch.ts

.gitmodules

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[submodule "vendor/RedCMD-TmLanguage-Syntax-Highlighter"]
2+
path = vendor/RedCMD-TmLanguage-Syntax-Highlighter
3+
url = https://github.com/RedCMD/TmLanguage-Syntax-Highlighter.git

javascript.tmLanguage.json

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@
1818
{
1919
"include": "#blockcomment"
2020
},
21+
{
22+
"include": "#regex-literal-prefix-ops"
23+
},
2124
{
2225
"include": "#regex"
2326
},
@@ -224,6 +227,35 @@
224227
}
225228
],
226229
"repository": {
230+
"regex-literal-prefix-ops": {
231+
"name": "string.regexp.js",
232+
"begin": "(?:(?<=[=|\\^&<>+\\-*%~(,.\\[?:{;])|(?<=\\binstanceof)|(?<=\\bin)|(?<=\\bnew)|(?<=\\bextends)|(?<=\\byield)|(?<=\\bget)|(?<=\\bset)|(?<=\\basync)|(?<=\\belse)|(?<=\\bdo)|(?<=\\breturn)|(?<=\\bthrow)|(?<=\\btry)|(?<=\\bfinally)|(?<=\\bcatch)|(?<=\\bof)|(?<=\\bcase)|(?<=\\bexport)|(?<=\\bdefault)|(?<=\\bimport)|(?<=\\bstatic)|(?<=\\baccessor)|(?<=\\btypeof)|(?<=\\bvoid)|(?<=\\bdelete)|(?<=\\bawait)|(?<=^))s*([!](?:s*[!])*)s*(?:((?:/\\*\\*(?!/)[\\s\\S]*?\\*/|/\\*[\\s\\S]*?\\*/)\\s*))?(/)(?![*/])",
233+
"beginCaptures": {
234+
"1": {
235+
"name": "keyword.operator.logical.prefix.js"
236+
},
237+
"2": {
238+
"name": "comment.block.js"
239+
},
240+
"3": {
241+
"name": "punctuation.definition.string.begin.regexp.js"
242+
}
243+
},
244+
"end": "(/)([gimsuydv]*)",
245+
"endCaptures": {
246+
"1": {
247+
"name": "punctuation.definition.string.end.regexp.js"
248+
},
249+
"2": {
250+
"name": "keyword.other.regexp.js"
251+
}
252+
},
253+
"patterns": [
254+
{
255+
"include": "#regexp"
256+
}
257+
]
258+
},
227259
"regexp": {
228260
"patterns": [
229261
{
@@ -1922,6 +1954,9 @@
19221954
{
19231955
"include": "#blockcomment"
19241956
},
1957+
{
1958+
"include": "#regex-literal-prefix-ops"
1959+
},
19251960
{
19261961
"include": "#regex"
19271962
},
@@ -2086,13 +2121,6 @@
20862121
}
20872122
]
20882123
},
2089-
"type": {
2090-
"patterns": [
2091-
{
2092-
"include": "#simple-type"
2093-
}
2094-
]
2095-
},
20962124
"qstring-double": {
20972125
"name": "string.quoted.double.js",
20982126
"begin": "\"",
@@ -2149,7 +2177,7 @@
21492177
},
21502178
"regex": {
21512179
"name": "string.regexp.js",
2152-
"begin": "(?:(?<=[=|\\^&<>+\\-*%~(,.\\[?:{;])|(?<=\\binstanceof)|(?<=\\bin)|(?<=\\bnew)|(?<=\\bextends)|(?<=\\byield)|(?<=\\bget)|(?<=\\bset)|(?<=\\basync)|(?<=\\belse)|(?<=\\bdo)|(?<=\\breturn)|(?<=\\bthrow)|(?<=\\btry)|(?<=\\bfinally)|(?<=\\bcatch)|(?<=\\bof)|(?<=\\bcase)|(?<=\\bexport)|(?<=\\bdefault)|(?<=\\bimport)|(?<=\\bstatic)|(?<=\\baccessor)|(?<=\\btypeof)|(?<=\\bvoid)|(?<=\\bdelete)|(?<=\\bawait)|(?<=(?:[=|\\^&<>+\\-*%~(,.\\[?:{;]|\\binstanceof|\\bin|\\bnew|\\bextends|\\byield|\\bget|\\bset|\\basync|\\belse|\\bdo|\\breturn|\\bthrow|\\btry|\\bfinally|\\bcatch|\\bof|\\bcase|\\bexport|\\bdefault|\\bimport|\\bstatic|\\baccessor|\\btypeof|\\bvoid|\\bdelete|\\bawait|^)\\s*[!](?:\\s*[!])*)|(?<=^))\\s*(?:((?:/\\*\\*(?!/)[\\s\\S]*?\\*/|/\\*[\\s\\S]*?\\*/)\\s*))?(/)(?![*/])",
2180+
"begin": "(?:(?<=[=|\\^&<>+\\-*%~(,.\\[?:{;])|(?<=\\binstanceof)|(?<=\\bin)|(?<=\\bnew)|(?<=\\bextends)|(?<=\\byield)|(?<=\\bget)|(?<=\\bset)|(?<=\\basync)|(?<=\\belse)|(?<=\\bdo)|(?<=\\breturn)|(?<=\\bthrow)|(?<=\\btry)|(?<=\\bfinally)|(?<=\\bcatch)|(?<=\\bof)|(?<=\\bcase)|(?<=\\bexport)|(?<=\\bdefault)|(?<=\\bimport)|(?<=\\bstatic)|(?<=\\baccessor)|(?<=\\btypeof)|(?<=\\bvoid)|(?<=\\bdelete)|(?<=\\bawait)|(?<=^))\\s*(?:((?:/\\*\\*(?!/)[\\s\\S]*?\\*/|/\\*[\\s\\S]*?\\*/)\\s*))?(/)(?![*/])",
21532181
"beginCaptures": {
21542182
"1": {
21552183
"name": "comment.block.js"

javascriptreact.tmLanguage.json

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@
2727
{
2828
"include": "#blockcomment"
2929
},
30+
{
31+
"include": "#regex-literal-prefix-ops"
32+
},
3033
{
3134
"include": "#regex"
3235
},
@@ -703,6 +706,35 @@
703706
}
704707
]
705708
},
709+
"regex-literal-prefix-ops": {
710+
"name": "string.regexp.js.jsx",
711+
"begin": "(?:(?<=[=|\\^&<>+\\-*%~(,.\\[?:{;])|(?<=\\binstanceof)|(?<=\\bin)|(?<=\\bnew)|(?<=\\bextends)|(?<=\\byield)|(?<=\\bget)|(?<=\\bset)|(?<=\\basync)|(?<=\\belse)|(?<=\\bdo)|(?<=\\breturn)|(?<=\\bthrow)|(?<=\\btry)|(?<=\\bfinally)|(?<=\\bcatch)|(?<=\\bof)|(?<=\\bcase)|(?<=\\bexport)|(?<=\\bdefault)|(?<=\\bimport)|(?<=\\bstatic)|(?<=\\baccessor)|(?<=\\btypeof)|(?<=\\bvoid)|(?<=\\bdelete)|(?<=\\bawait)|(?<=^))s*([!](?:s*[!])*)s*(?:((?:/\\*\\*(?!/)[\\s\\S]*?\\*/|/\\*[\\s\\S]*?\\*/)\\s*))?(/)(?![*/])",
712+
"beginCaptures": {
713+
"1": {
714+
"name": "keyword.operator.logical.prefix.js.jsx"
715+
},
716+
"2": {
717+
"name": "comment.block.js.jsx"
718+
},
719+
"3": {
720+
"name": "punctuation.definition.string.begin.regexp.js.jsx"
721+
}
722+
},
723+
"end": "(/)([gimsuydv]*)",
724+
"endCaptures": {
725+
"1": {
726+
"name": "punctuation.definition.string.end.regexp.js.jsx"
727+
},
728+
"2": {
729+
"name": "keyword.other.regexp.js.jsx"
730+
}
731+
},
732+
"patterns": [
733+
{
734+
"include": "#regexp"
735+
}
736+
]
737+
},
706738
"regexp": {
707739
"patterns": [
708740
{
@@ -2410,6 +2442,9 @@
24102442
{
24112443
"include": "#blockcomment"
24122444
},
2445+
{
2446+
"include": "#regex-literal-prefix-ops"
2447+
},
24132448
{
24142449
"include": "#regex"
24152450
},
@@ -2574,13 +2609,6 @@
25742609
}
25752610
]
25762611
},
2577-
"type": {
2578-
"patterns": [
2579-
{
2580-
"include": "#simple-type"
2581-
}
2582-
]
2583-
},
25842612
"qstring-double": {
25852613
"name": "string.quoted.double.js.jsx",
25862614
"begin": "\"",
@@ -2637,7 +2665,7 @@
26372665
},
26382666
"regex": {
26392667
"name": "string.regexp.js.jsx",
2640-
"begin": "(?:(?<=[=|\\^&<>+\\-*%~(,.\\[?:{;])|(?<=\\binstanceof)|(?<=\\bin)|(?<=\\bnew)|(?<=\\bextends)|(?<=\\byield)|(?<=\\bget)|(?<=\\bset)|(?<=\\basync)|(?<=\\belse)|(?<=\\bdo)|(?<=\\breturn)|(?<=\\bthrow)|(?<=\\btry)|(?<=\\bfinally)|(?<=\\bcatch)|(?<=\\bof)|(?<=\\bcase)|(?<=\\bexport)|(?<=\\bdefault)|(?<=\\bimport)|(?<=\\bstatic)|(?<=\\baccessor)|(?<=\\btypeof)|(?<=\\bvoid)|(?<=\\bdelete)|(?<=\\bawait)|(?<=(?:[=|\\^&<>+\\-*%~(,.\\[?:{;]|\\binstanceof|\\bin|\\bnew|\\bextends|\\byield|\\bget|\\bset|\\basync|\\belse|\\bdo|\\breturn|\\bthrow|\\btry|\\bfinally|\\bcatch|\\bof|\\bcase|\\bexport|\\bdefault|\\bimport|\\bstatic|\\baccessor|\\btypeof|\\bvoid|\\bdelete|\\bawait|^)\\s*[!](?:\\s*[!])*)|(?<=^))\\s*(?:((?:/\\*\\*(?!/)[\\s\\S]*?\\*/|/\\*[\\s\\S]*?\\*/)\\s*))?(/)(?![*/])",
2668+
"begin": "(?:(?<=[=|\\^&<>+\\-*%~(,.\\[?:{;])|(?<=\\binstanceof)|(?<=\\bin)|(?<=\\bnew)|(?<=\\bextends)|(?<=\\byield)|(?<=\\bget)|(?<=\\bset)|(?<=\\basync)|(?<=\\belse)|(?<=\\bdo)|(?<=\\breturn)|(?<=\\bthrow)|(?<=\\btry)|(?<=\\bfinally)|(?<=\\bcatch)|(?<=\\bof)|(?<=\\bcase)|(?<=\\bexport)|(?<=\\bdefault)|(?<=\\bimport)|(?<=\\bstatic)|(?<=\\baccessor)|(?<=\\btypeof)|(?<=\\bvoid)|(?<=\\bdelete)|(?<=\\bawait)|(?<=^))\\s*(?:((?:/\\*\\*(?!/)[\\s\\S]*?\\*/|/\\*[\\s\\S]*?\\*/)\\s*))?(/)(?![*/])",
26412669
"beginCaptures": {
26422670
"1": {
26432671
"name": "comment.block.js.jsx"

package-lock.json

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

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
"conformance:tsx": "node test/tsx-conformance.ts",
1111
"conformance:jsx": "node test/jsx-conformance.ts",
1212
"conformance:html": "node test/html-conformance.ts",
13+
"test:tm-diagnostics": "node test/redcmd-tm-diagnostics.ts",
1314
"spike:html-lexer": "node test/html-lexer-spike.ts",
1415
"bench:html-official": "node test/html-bench.ts",
1516
"bench:html-embed": "node test/html-embed-js.ts",
@@ -49,6 +50,7 @@
4950
"parse5": "^8.0.1",
5051
"tree-sitter-cli": "^0.26.9",
5152
"typescript": "^5.6.0",
53+
"vscode-onigmo": "^2.0.1",
5254
"vscode-oniguruma": "^2.0.1",
5355
"vscode-textmate": "^9.3.2",
5456
"vscode-tmlanguage-snapshot": "^1.0.1",

src/gen-tm.ts

Lines changed: 45 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -833,6 +833,14 @@ function buildOperandStartClass(grammar: CstGrammar, identToken: TokenDecl | und
833833
return `[[:alpha:][:digit:]${cls}]`;
834834
}
835835

836+
function notAfterValueWithOptionalWhitespace(valueCharClass: string, maxWhitespace = 16): string {
837+
const assertions: string[] = [];
838+
for (let spaces = 0; spaces <= maxWhitespace; spaces++) {
839+
assertions.push(`(?<![${valueCharClass}]${'\\s'.repeat(spaces)})`);
840+
}
841+
return assertions.join('');
842+
}
843+
836844
// ── JSX detection ──
837845

838846
interface JsxInfo {
@@ -992,6 +1000,7 @@ interface JsxDisambigDelims {
9921000
topTypeParam: string; // "is a type-param list" body: top-level comma OR constraint keyword
9931001
balancedAngles: string; // recursive balanced `<…>` named group `(?<B>…)`
9941002
arrowParamShape: string; // the arrow-shaped `(` confirm after `>`
1003+
close: string; // the generic close delimiter (`>` for TS/TSX)
9951004
// Lookbehind body asserting the `>` just left closes a type-param LIST that carried a
9961005
// top-level comma or constraint keyword (`<T,>`, `<T extends X>`) — i.e. the SAME
9971006
// generic-arrow disambiguation signal as `topTypeParam`, but for matching a `(` that
@@ -1126,7 +1135,7 @@ function jsxDisambigDelims(grammar: CstGrammar, identRegex: string, separator: s
11261135
? `|${skip}\\b(?:${constraintKeywords.map(escapeRegex).join('|')})\\b`
11271136
: '';
11281137
const typeParamCloseBehind = `${escapeRegex(open)}(?:${topComma}${behindKw})${skip}${escapeRegex(close)}`;
1129-
return { topComma, topTypeParam, balancedAngles, arrowParamShape, typeParamCloseBehind };
1138+
return { topComma, topTypeParam, balancedAngles, arrowParamShape, close, typeParamCloseBehind };
11301139
}
11311140

11321141
/**
@@ -1916,12 +1925,12 @@ function generateTypeCastPattern(
19161925
const tpEnd = `punctuation.definition.typeparameters.end.${langName}`;
19171926
// `<` only at expression-start. A prefix cast's `<` is never preceded by a value
19181927
// OPERAND; a comparison's `<` always is (`a < b`). Reject the cast when `<` is
1919-
// preceded — across any whitespace — by an operand-ending char: an identifier
1928+
// preceded — across bounded whitespace — by an operand-ending char: an identifier
19201929
// char, `)`, `]`, a numeric/quote tail. This keeps `a < b > c`, `f() < g`,
1921-
// `x] < y` as comparisons (variable-length lookbehind; Oniguruma supports it).
1930+
// `x] < y` as comparisons while staying compatible with TextMate 2.0 Onigmo.
19221931
// Casts after a keyword that ends in a letter (`return <T>x`) stay a comparison
19231932
// here — rare, and never a regression (they were unhighlighted before too).
1924-
const notAfter = `(?<![\\w$)\\]]\\s*)`;
1933+
const notAfter = notAfterValueWithOptionalWhitespace('\\w$)\\]');
19251934
// Type-shaped, balanced-angle inner content (kept to type characters so an
19261935
// ordinary `a < b > c` comparison — whose operands are arbitrary expressions —
19271936
// is not swallowed). `\g<TC>` recurses for nested generics like `<Array<T>>`.
@@ -2445,26 +2454,7 @@ function generateRegexLiteralPatterns(
24452454
// Also match at start of line
24462455
const startOfLine = '(?<=^)';
24472456

2448-
// Ambiguous postfix/prefix op chars (TS `!`): a `/` may follow one ONLY when the op-run is
2449-
// the PREFIX form — i.e. the run is itself in a regex-start position (`= !/re/`, `!!/re/`,
2450-
// `return !/x/`), NOT the postfix non-null form (`x! / y` → division). We can't decide that
2451-
// from the single char before `/` (it's the op either way), so look back PAST the op-run and
2452-
// re-apply the same regex-start test there. The inner context is the SAME char-class +
2453-
// keywords + line-start used above, but un-wrapped (it sits inside this lookbehind), and the
2454-
// op-run is `[ops](?:\s*[ops])*` (chained `!!` allowed). Because these chars were excluded
2455-
// from `charLookbehind`, a postfix op (preceded by a value) matches NONE of the alternatives
2456-
// → the `/` falls through to the division operator.
2457-
const innerCtx = [
2458-
charEsc ? `[${charEsc}]` : null,
2459-
...info.preceedingKeywords.map(kw => `\\b${escapeRegex(kw)}`),
2460-
'^',
2461-
].filter(Boolean).join('|');
2462-
const opRun = info.postfixAmbiguousChars.map(escapeRegex).join('');
2463-
const postfixBangLookbehind = opRun
2464-
? `(?<=(?:${innerCtx})\\s*[${opRun}](?:\\s*[${opRun}])*)`
2465-
: '';
2466-
2467-
const lbAlts = [charLookbehind, keywordLookbehinds, postfixBangLookbehind, startOfLine]
2457+
const lbAlts = [charLookbehind, keywordLookbehinds, startOfLine]
24682458
.filter(Boolean).join('|');
24692459
const fullLookbehind = `(?:${lbAlts})`;
24702460

@@ -2491,6 +2481,31 @@ function generateRegexLiteralPatterns(
24912481
};
24922482
if (commentBody) beginCaptures['1'] = { name: `comment.block.${langName}` };
24932483

2484+
// Ambiguous postfix/prefix op chars (TS `!`): a `/` may follow one ONLY when the op-run is
2485+
// the PREFIX form (`= !/re/`, `return !!/x/`), not postfix non-null (`x! / y`). TextMate 2.0's
2486+
// Onigmo rejects the old variable-length lookbehind that looked past the whole op-run, so this
2487+
// separate pattern anchors on the fixed-width expression-start context and consumes the op-run.
2488+
const prefixOpClass = info.postfixAmbiguousChars.map(escapeForCharClass).join('');
2489+
if (prefixOpClass) {
2490+
const prefixSlashGroup = commentBody ? '3' : '2';
2491+
const prefixCaptures: Record<string, { name: string }> = {
2492+
'1': { name: `keyword.operator.logical.prefix.${langName}` },
2493+
[prefixSlashGroup]: { name: `punctuation.definition.string.begin.regexp.${langName}` },
2494+
};
2495+
if (commentBody) prefixCaptures['2'] = { name: `comment.block.${langName}` };
2496+
result['regex-literal-prefix-ops'] = {
2497+
name: `string.regexp.${langName}`,
2498+
begin: `${fullLookbehind}\s*([${prefixOpClass}](?:\s*[${prefixOpClass}])*)\s*${commentPrefix}(/)${commentExclude}`,
2499+
beginCaptures: prefixCaptures,
2500+
end: `(/)(${info.flagsPattern})`,
2501+
endCaptures: {
2502+
'1': { name: `punctuation.definition.string.end.regexp.${langName}` },
2503+
'2': { name: `keyword.other.regexp.${langName}` },
2504+
},
2505+
patterns: [{ include: '#regexp' }],
2506+
};
2507+
}
2508+
24942509
result['regex-literal'] = {
24952510
name: `string.regexp.${langName}`,
24962511
begin: `${fullLookbehind}\\s*${commentPrefix}(/)${commentExclude}`,
@@ -4382,6 +4397,7 @@ export function generateTmLanguage(grammar: CstGrammar, langName: string): TmGra
43824397
for (const [key, pattern] of Object.entries(rlPatterns)) {
43834398
repository[key] = pattern;
43844399
}
4400+
if (rlPatterns['regex-literal-prefix-ops']) topPatterns.push({ include: '#regex-literal-prefix-ops' });
43854401
topPatterns.push({ include: '#regex-literal' });
43864402
}
43874403

@@ -4937,7 +4953,7 @@ export function generateTmLanguage(grammar: CstGrammar, langName: string): TmGra
49374953
// reference it via `{ include: '#type-inner' }`. No shared mutable array;
49384954
// later injections rebuild the patterns array non-destructively.
49394955
// Type operators are derived from @type rule literals.
4940-
const typeInnerPats: (TmPattern | { include: string })[] = [
4956+
const typeInnerPats: (TmPattern | { include: string })[] = hasTypeAnnotations ? [
49414957
...(repository['generic-type'] ? [{ include: '#generic-type' }] : []),
49424958
...(repository['type-object-type'] ? [{ include: '#type-object-type' }] : []),
49434959
...(repository['type-paren'] ? [{ include: '#type-paren' }] : []),
@@ -4947,7 +4963,7 @@ export function generateTmLanguage(grammar: CstGrammar, langName: string): TmGra
49474963
// swallowed by the surrounding type region's name.
49484964
...literalTypeIncludes,
49494965
{ include: '#simple-type' },
4950-
];
4966+
] : [];
49514967
// Union/intersection operators — only if present in @type rules
49524968
const typeUnionOps = ['|', '&'].filter(op => typeLiterals.has(op));
49534969
if (typeUnionOps.length > 0) {
@@ -5028,7 +5044,7 @@ export function generateTmLanguage(grammar: CstGrammar, langName: string): TmGra
50285044
typeInnerPats.splice(idx === -1 ? typeInnerPats.length : idx, 0, { include: '#type-conditional' });
50295045
}
50305046

5031-
repository['type-inner'] = { patterns: typeInnerPats };
5047+
if (hasTypeAnnotations) repository['type-inner'] = { patterns: typeInnerPats };
50325048

50335049
// Wire up deferred type-paren pattern (basic wiring; patched after type injections)
50345050
if (repository['type-paren']) {
@@ -5405,7 +5421,7 @@ export function generateTmLanguage(grammar: CstGrammar, langName: string): TmGra
54055421
if (angleBracket && angleDisambig) {
54065422
const balancedAngles = angleDisambig.balancedAngles;
54075423
const arrowParamShape = angleDisambig.arrowParamShape;
5408-
const arrowPos = `(?:(?<=\\basync\\s)|(?<![\\w$)\\]}]\\s*))`;
5424+
const arrowPos = `(?:(?<=\\basync\\s)|${notAfterValueWithOptionalWhitespace('\\w$)\\]}')})`;
54095425
// JSX-dialect disambiguator: in a `.tsx`/`.jsx` grammar a bare `<Foo>(…`
54105426
// is a JSX element, so a generic-arrow type-param list is only recognised
54115427
// when it carries a TOP-LEVEL comma inside the `<…>` (`<T,>`, `<T = X,>`,
@@ -6366,7 +6382,7 @@ export function generateTmLanguage(grammar: CstGrammar, langName: string): TmGra
63666382
if (angleBracket && angleDisambig) {
63676383
repository['arrow-function-params-generic'] = {
63686384
name: `meta.parameters.arrow.${langName}`,
6369-
begin: `(?<=${angleDisambig.typeParamCloseBehind})\\s*(\\()\\s*$`,
6385+
begin: `(?<=${escapeRegex(angleDisambig.close)})\\s*(\\()\\s*$`,
63706386
beginCaptures: {
63716387
'1': { name: `punctuation.definition.parameters.begin.${langName}` },
63726388
},

0 commit comments

Comments
 (0)