Skip to content

Commit bc1b10c

Browse files
committed
Scope a YAML block-scalar explicit key like a block scalar, not a name
A block scalar used as an explicit key (`? |` / `? >`) was scoped entirely as `entity.name.tag` — including the `|`/`>` introducer. But `|`/`>` is a block-scalar control sigil, never a name (this is exactly what fca5499 fixed for value-position block scalars); painting it as a name in key position was the same mis-scope left on one branch. And once the introducer is a keyword, the body should be a string too, for coherence: a block scalar is `<keyword indicator><string body>` everywhere, and the key-ness of a `? |` key is already carried by the `?` (map-key punctuation) and the `:` separator — recolouring the body as a name only in key position makes the construct look unlike itself depending on position. The maintained official grammar does the coherent thing, and loses no information. Now `? |\n k\n: v` → `?`=punctuation.definition.map.key, `|`=keyword.control.flow. block-scalar, body=string.unquoted.block (matching official). The oracle splits a block-scalar key the same as a value (block-indicator + string body), so both grammars are graded correct — the earlier false "Monogram-only" win is gone and the official baseline is no longer unfairly penalised (YAML official 98.9% -> 99.0%, gap +0.3 -> +0.2; Monogram still 99.2%). Parser 100% aligned; other six grammars byte-identical.
1 parent 4d25dba commit bc1b10c

3 files changed

Lines changed: 21 additions & 16 deletions

File tree

src/gen-tm.ts

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4636,6 +4636,8 @@ export function generateTmLanguage(grammar: CstGrammar, langName: string): TmGra
46364636
let bsIntro = '';
46374637
let bsFunkyIntroRule: ((indicatorScope: string, contentScope: string) => TmPattern) | null = null;
46384638
let bsHeaderIncludes: { include: string }[] = [];
4639+
let bsIndicatorScope = '';
4640+
let bsContentScope = '';
46394641
if (blockScalar) {
46404642
const bsTok = grammar.tokens.find(t => t.name === blockScalar.token);
46414643
const bsKey = blockScalar.token.toLowerCase();
@@ -4736,6 +4738,8 @@ export function generateTmLanguage(grammar: CstGrammar, langName: string): TmGra
47364738
bsIntro = intro;
47374739
bsFunkyIntroRule = bsIntroRule;
47384740
bsHeaderIncludes = bsHeaderIncs;
4741+
bsIndicatorScope = bsIndicator;
4742+
bsContentScope = bsContent;
47394743
repository[bsKey] = {
47404744
begin: `^([ \\t]*)(?=${bsVp}${intro}[\\t ]*(?:#|$))`,
47414745
while: '\\G(?=\\1[ \\t]|[ \\t]*$)',
@@ -4826,20 +4830,20 @@ export function generateTmLanguage(grammar: CstGrammar, langName: string): TmGra
48264830
topPatterns.push({ include: '#explicit-key-indicator' });
48274831

48284832
// A block scalar can ALSO be an explicit key (`? |` / `? >`). An implicit key must be a single
4829-
// line, so a multi-line block scalar key is ALWAYS `?`-introduced — and like every other scalar
4830-
// key (plain / quoted, both already `entity.name.tag`) its content is the KEY NAME, not a value
4831-
// string. Same PORTABLE structure as §2a (forward-captured node indent + funky body), but gated on
4832-
// the `?` indicator and scoping the introducer + body with the KEY scope. The `?` is captured as
4833-
// map-key punctuation; the inner introducer rule scopes the `|`/`>` and the body as the key name.
4834-
// Ranked above the value-position block scalar (scopeOrder) so `? |` wins; a `: |` value has no
4835-
// leading `?`, so it is untouched.
4833+
// line, so a multi-line block scalar key is ALWAYS `?`-introduced. The block scalar itself is
4834+
// scoped like ANY block scalar — introducer (`|`/`>`) → the block-scalar keyword, body →
4835+
// string.unquoted.block — and the KEY-ness is carried by the `?` (map-key punctuation) + the `:`
4836+
// separator, NOT by recolouring the body as a name. (`|`/`>` is a control sigil, never a name; and
4837+
// a block scalar should read the SAME in key or value position — the official grammar does exactly
4838+
// this.) Same PORTABLE structure as §2a (forward-captured node indent + funky body), gated on the
4839+
// `?` indicator. Ranked above the value-position block scalar (scopeOrder) so `? |` wins; a `: |`
4840+
// value has no leading `?`, so it is untouched.
48364841
if (blockScalar && bsFunkyIntroRule) {
4837-
const keyScope = `${explicitKey.keyScope}.${langName}`;
48384842
repository['blockscalar-key'] = {
48394843
begin: `^([ \\t]*)(${escapeRegex(explicitKey.indicator)})([\\t ]+)(?=${bsIntro}[\\t ]*(?:#|$))`,
48404844
beginCaptures: { '2': { name: `punctuation.definition.map.key.${langName}` } },
48414845
while: '\\G(?=\\1[ \\t]|[ \\t]*$)',
4842-
patterns: [bsFunkyIntroRule(keyScope, keyScope), ...bsHeaderIncludes],
4846+
patterns: [bsFunkyIntroRule(bsIndicatorScope, bsContentScope), ...bsHeaderIncludes],
48434847
};
48444848
topPatterns.push({ include: '#blockscalar-key' });
48454849
}

test/yaml-oracle.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,10 @@ export function yamlOracle(text: string): GoldToken[] {
3737
ESCAPE.lastIndex = 0;
3838
while ((m = ESCAPE.exec(seg))) out.push({ start: r[0] + m.index, end: r[0] + m.index + m[0].length, text: m[0], role: R.escape });
3939
};
40-
// A VALUE-position block scalar (`|`/`>`): split the introducer (a structural control sigil) from
41-
// the verbatim body. introducer = `|`/`>` + chomping/indent on the header line; body = the lines
42-
// below. (A block scalar in KEY position stays ONE tagName token — the whole scalar is the key name.)
40+
// A block scalar (`|`/`>`), in KEY or VALUE position: split the introducer (a structural control
41+
// sigil → block-indicator) from the verbatim body (a string). introducer = `|`/`>` + chomping/indent
42+
// on the header line; body = the lines below. A block scalar reads the SAME wherever it sits — the
43+
// key-ness of a `? |` key is carried by the `?`/`:`, NOT by recolouring the body as a name.
4344
const isBlockScalar = (n: any) => n?.type === 'BLOCK_LITERAL' || n?.type === 'BLOCK_FOLDED';
4445
const pushBlockScalar = (node: any): void => {
4546
const r = node?.range;
@@ -57,7 +58,7 @@ export function yamlOracle(text: string): GoldToken[] {
5758
const walk = (node: any, isKey: boolean): void => {
5859
if (!node) return;
5960
if (isScalar(node)) {
60-
if (!isKey && isBlockScalar(node)) pushBlockScalar(node);
61+
if (isBlockScalar(node)) pushBlockScalar(node);
6162
else { push(node, isKey ? R.tagName : valueRole(node.value)); pushEscapes(node); }
6263
}
6364
else if (isMap(node)) for (const p of node.items) { walk(p.key, true); walk(p.value, false); }

yaml.tmLanguage.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -631,7 +631,7 @@
631631
"begin": "[\\t ]*([|>](?:[1-9][-+]?|[-+][1-9]?|[-+])?)(?=[\\t ]*(?:#|$))([\\t ]*.*)",
632632
"beginCaptures": {
633633
"1": {
634-
"name": "entity.name.tag.yaml"
634+
"name": "keyword.control.flow.block-scalar.yaml"
635635
},
636636
"2": {
637637
"patterns": [
@@ -650,7 +650,7 @@
650650
{
651651
"begin": "\\G( ++)$",
652652
"while": "\\G(?>(\\1)$|(?!\\1)( *+)($|.))",
653-
"contentName": "entity.name.tag.yaml"
653+
"contentName": "string.unquoted.block.yaml"
654654
},
655655
{
656656
"begin": "\\G(?!$)(?=( *+))",
@@ -659,7 +659,7 @@
659659
{
660660
"begin": "\\G( *+)",
661661
"while": "\\G(?>(\\1)|( *+)($|[^\\t#]|[\\t ]++[^#]))",
662-
"contentName": "entity.name.tag.yaml"
662+
"contentName": "string.unquoted.block.yaml"
663663
}
664664
]
665665
},

0 commit comments

Comments
 (0)