Skip to content

Commit 87ab7e3

Browse files
committed
Add YAML to the README issue ledger
The per-language issue ledger covered TS/TSX/HTML/Vue but not YAML. Add a YAML section: 9 real upstream YAML-grammar bugs as data in test/yaml-issue-cases.ts (same {id,title,src,at,want} shape as the others), wired into test/issue-table.ts (loads the maintained RedCMD official grammar + its version sub-grammars, grades each case against it and Monogram, emits the table). Each case cites the underlying tracker — the canonical aggregator is RedCMD/YAML-Syntax-Highlighter#1 ("Pre-existing grammar issues"), which links the VS Code / textmate-yaml.tmbundle / atom-language-yaml issues: e.g. tmbundle#38 (block scalar mis-colored after a leading newline), atom/language-yaml#114 (`#` in a block body), #119 (escapes in a quoted key), vscode#170032 (frontmatter `---`), tmbundle#18 (`{}` in literal style), #39 (int-vs-string). Result: YAML Monogram 9/9 vs the maintained official 8/9 — eight both handle, and the block-scalar-used-as-an-explicit-key is scoped as a key name only by Monogram. linkify extended for the atom/RedCMD owner/repo slugs and a per-language tmbundle prefix; the TS/TSX/HTML/Vue sections are byte-identical.
1 parent 94f2f04 commit 87ab7e3

3 files changed

Lines changed: 124 additions & 5 deletions

File tree

README.md

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ The same question, every language at once: take the bugs reported against each *
6666

6767
<!-- issues:start -->
6868
<!-- generated by `npm run bench:issues` — do not edit by hand -->
69-
_Each hand-written **official** grammar vs Monogram's **derived** one, on the bugs filed against it: **TypeScript 26/26** (official 8/26) · **TSX 11/11** (official 5/11) · **HTML 20/20** (official 13/20) · **Vue 23/23** (official 18/23). Per-issue detail below — auto-generated by `npm run bench:issues`._
69+
_Each hand-written **official** grammar vs Monogram's **derived** one, on the bugs filed against it: **TypeScript 26/26** (official 8/26) · **TSX 11/11** (official 5/11) · **HTML 20/20** (official 13/20) · **Vue 23/23** (official 18/23) · **YAML 9/9** (official 8/9). Per-issue detail below — auto-generated by `npm run bench:issues`._
7070

7171
#### TypeScript
7272
| issue | Monogram | official |
@@ -190,6 +190,26 @@ _Each hand-written **official** grammar vs Monogram's **derived** one, on the bu
190190
| [#2666](https://github.com/vuejs/language-tools/issues/2666) — dynamic slot name from a template literal |||
191191
| [#2560](https://github.com/vuejs/language-tools/issues/2560)/[#1290](https://github.com/vuejs/language-tools/issues/1290)`type` as a v-for loop variable |||
192192

193+
</details>
194+
195+
#### YAML
196+
| issue | Monogram | official |
197+
|---|:--:|:--:|
198+
| [RedCMD/YAML-Syntax-Highlighter#1](https://github.com/RedCMD/YAML-Syntax-Highlighter/issues/1) — a block scalar used AS an explicit key is scoped as a key name || · |
199+
200+
<details><summary>… and 8 more both grammars already handle (✓ / ✓)</summary>
201+
202+
| issue | Monogram | official |
203+
|---|:--:|:--:|
204+
| [vscode#170032](https://github.com/microsoft/vscode/issues/170032) — document markers `---` / `...` are document structure, not stray punctuation |||
205+
| [atom/language-yaml#114](https://github.com/atom/language-yaml/issues/114) — a `#` in a block-scalar body is content, not a comment |||
206+
| [tmbundle#38](https://github.com/textmate/yaml.tmbundle/issues/38) — a block scalar with leading/internal EMPTY lines stays one string region |||
207+
| [tmbundle#18](https://github.com/textmate/yaml.tmbundle/issues/18) — JSON-ish punctuation (`{`/`}`) and a tab indicator inside a block scalar stay content |||
208+
| [RedCMD/YAML-Syntax-Highlighter#1](https://github.com/RedCMD/YAML-Syntax-Highlighter/issues/1) — an anchor `&a` in explicit-key (`?`) position is still an anchor |||
209+
| [RedCMD/YAML-Syntax-Highlighter#1](https://github.com/RedCMD/YAML-Syntax-Highlighter/issues/1) — a bare `?` opening an explicit multi-line sequence key is the map-key indicator |||
210+
| [atom/language-yaml#119](https://github.com/atom/language-yaml/issues/119) — an escape inside a double-quoted KEY is highlighted |||
211+
| [tmbundle#39](https://github.com/textmate/yaml.tmbundle/issues/39) — a plain scalar resolving to `null` is lexically a string that resolves to a constant |||
212+
193213
</details>
194214
<!-- issues:end -->
195215

test/issue-table.ts

Lines changed: 51 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,13 @@
1616
import vsctm from 'vscode-textmate';
1717
import onig from 'vscode-oniguruma';
1818
import { readFileSync, existsSync, writeFileSync } from 'node:fs';
19+
import { dirname, join } from 'node:path';
1920
import { createRequire } from 'node:module';
2021
import { tests as tsTests } from './issue-cases.ts';
2122
import { cases as tsxCases } from './tsx-issue-cases.ts';
2223
import { cases as htmlCases } from './html-issue-cases.ts';
2324
import { cases as vueCases } from './vue-issue-cases.ts';
25+
import { cases as yamlCases } from './yaml-issue-cases.ts';
2426
import { scopeLookup as vueScopeLookup, officialAvailable as vueOfficialAvailable } from './vue-grammar-harness.ts';
2527

2628
const { INITIAL, Registry, parseRawGrammar } = vsctm;
@@ -37,6 +39,11 @@ const official = {
3739
html: process.env.MONOGRAM_OFFICIAL_HTML ?? `${VSCODE}/html/syntaxes/html.tmLanguage.json`,
3840
js: process.env.MONOGRAM_OFFICIAL_JS ?? `${VSCODE}/javascript/syntaxes/JavaScript.tmLanguage.json`,
3941
css: process.env.MONOGRAM_OFFICIAL_CSS ?? `${VSCODE}/css/syntaxes/css.tmLanguage.json`,
42+
// YAML's official baseline is the MAINTAINED RedCMD/YAML-Syntax-Highlighter (microsoft/vscode#232244
43+
// switched VS Code off the dead textmate/yaml.tmbundle to it). Default = the clone at /tmp/redcmd-yaml
44+
// (same as test/scope-gap-yaml.ts); VS Code's bundled YAML is the same grammar — point
45+
// MONOGRAM_OFFICIAL_YAML at .../extensions/yaml/syntaxes/yaml.tmLanguage.json for that.
46+
yaml: process.env.MONOGRAM_OFFICIAL_YAML ?? '/tmp/redcmd-yaml/syntaxes/yaml.tmLanguage.json',
4047
};
4148

4249
function scopeAtFns(grammar: any, src: string) {
@@ -133,20 +140,60 @@ async function gradeVue(): Promise<Row[] | null> {
133140
return rows;
134141
}
135142

143+
// ── YAML: cases are {id, title, src, at, want} (predicate-based, like HTML). Monogram's yaml
144+
// (source.yaml) vs the MAINTAINED RedCMD/YAML-Syntax-Highlighter — the same "official" baseline as
145+
// test/scope-gap-yaml.ts. That grammar is a dispatcher stub that include()s version-specific
146+
// sub-grammars (source.yaml.1.2 etc.) in the same syntaxes/ dir; load them all, or the official
147+
// scopes nothing. Both grammars are self-contained YAML (no embeds reached by these snippets).
148+
async function gradeYaml(): Promise<Row[] | null> {
149+
if (!existsSync(official.yaml)) return null;
150+
const syn = dirname(official.yaml);
151+
const yamlExtra: Record<string, string> = {
152+
'source.yaml.1.2': join(syn, 'yaml-1.2.tmLanguage.json'),
153+
'source.yaml.1.1': join(syn, 'yaml-1.1.tmLanguage.json'),
154+
'source.yaml.1.0': join(syn, 'yaml-1.0.tmLanguage.json'),
155+
'source.yaml.1.3': join(syn, 'yaml-1.3.tmLanguage.json'),
156+
'source.yaml.embedded': join(syn, 'yaml-embedded.tmLanguage.json'),
157+
};
158+
const mk = (path: string, extra: Record<string, string>) => new Registry({ onigLib, loadGrammar: async (sn) =>
159+
sn === 'source.yaml' ? parseRawGrammar(read(path), 'yaml.json') :
160+
(extra[sn] && existsSync(extra[sn])) ? parseRawGrammar(read(extra[sn]), `${sn}.json`) :
161+
(sn.startsWith('source.') ? stub(sn) : null) });
162+
const mono = (await mk('yaml.tmLanguage.json', {}).loadGrammar('source.yaml'))!;
163+
const off = (await mk(official.yaml, yamlExtra).loadGrammar('source.yaml'))!;
164+
return yamlCases.map(c => ({ id: c.id, title: c.title, mono: c.want(scopeAtFns(mono, c.src)(c.at, c.nth)), off: c.want(scopeAtFns(off, c.src)(c.at, c.nth)) }));
165+
}
166+
136167
const langs: { name: string; key: string; opponent: string; rows: Row[] | null }[] = [
137168
{ name: 'TypeScript', key: 'ts', opponent: 'microsoft/TypeScript-TmLanguage', rows: await gradeTs() },
138169
{ name: 'TSX', key: 'tsx', opponent: 'microsoft/TypeScript-TmLanguage (TypeScriptReact)', rows: await gradeTsx() },
139170
{ name: 'HTML', key: 'html', opponent: "VS Code's html.tmLanguage", rows: await gradeHtml() },
140171
{ name: 'Vue', key: 'vue', opponent: 'vuejs/language-tools vue.tmLanguage.json', rows: await gradeVue() },
172+
{ name: 'YAML', key: 'yaml', opponent: 'RedCMD/YAML-Syntax-Highlighter yaml.tmLanguage.json', rows: await gradeYaml() },
141173
];
142174

143175
// ── render ──
144176
const mark = (b: boolean) => b ? '✓' : '·';
145177
// Link each tracker id to its issue. ids look like `#1050`, `tmbundle#118`, `vscode#140360`,
146-
// or `#6007/#2096/#520`; the prefix (or the language) selects the repo.
147-
const REPO: Record<string, string> = { ts: 'microsoft/TypeScript-TmLanguage', tsx: 'microsoft/TypeScript-TmLanguage', html: 'textmate/html.tmbundle', vue: 'vuejs/language-tools' };
148-
const PREFIX: Record<string, string> = { tmbundle: 'textmate/html.tmbundle', vscode: 'microsoft/vscode' };
149-
const linkify = (id: string, key: string) => id.replace(/([a-z]+)?#(\d+)/g, (_m, pfx, num) => `[${pfx ?? ''}#${num}](https://github.com/${(pfx && PREFIX[pfx]) || REPO[key]}/issues/${num})`);
178+
// `#6007/#2096/#520`, a full `owner/repo#NN` slug (`atom/language-yaml#119`,
179+
// `RedCMD/YAML-Syntax-Highlighter#1`), or a bare short prefix (`tmbundle#38`). The slug, the short
180+
// prefix (resolved per-language so `tmbundle` → html vs yaml bundle), or the language selects the repo.
181+
const REPO: Record<string, string> = { ts: 'microsoft/TypeScript-TmLanguage', tsx: 'microsoft/TypeScript-TmLanguage', html: 'textmate/html.tmbundle', vue: 'vuejs/language-tools', yaml: 'RedCMD/YAML-Syntax-Highlighter' };
182+
// Short prefixes → repo. `tmbundle` is language-specific (the html vs the yaml bundle), so it's keyed
183+
// per language; the rest are global. An unknown prefix falls back to the language's REPO.
184+
const PREFIX: Record<string, string> = { vscode: 'microsoft/vscode' };
185+
const PREFIX_BY_LANG: Record<string, Record<string, string>> = {
186+
html: { tmbundle: 'textmate/html.tmbundle' },
187+
yaml: { tmbundle: 'textmate/yaml.tmbundle', atom: 'atom/language-yaml' },
188+
};
189+
// Match either a full `owner/repo` slug (contains a `/`, allows mixed case) OR a bare short prefix
190+
// (lowercase letters) OR nothing, immediately before `#NN`.
191+
const linkify = (id: string, key: string) => id.replace(/((?:[\w.-]+\/[\w.-]+)|[a-z]+)?#(\d+)/g, (_m, pfx, num) => {
192+
const repo = !pfx ? REPO[key]
193+
: pfx.includes('/') ? pfx // already a full owner/repo slug
194+
: (PREFIX_BY_LANG[key]?.[pfx] ?? PREFIX[pfx] ?? REPO[key]); // a short prefix
195+
return `[${pfx ?? ''}#${num}](https://github.com/${repo}/issues/${num})`;
196+
});
150197
const graded = langs.filter(l => l.rows) as { name: string; key: string; opponent: string; rows: Row[] }[];
151198
// One-line tally + per-language detail, written into a single auto-generated region.
152199
// The summary used to be a table, but it just duplicated the detail below — collapse it to

test/yaml-issue-cases.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// yaml-issue-cases.ts — REAL highlighting issues reported against the YAML grammar, as DATA (no
2+
// side effects on import). The "official" YAML grammar is the MAINTAINED RedCMD/YAML-Syntax-
3+
// Highlighter (microsoft/vscode#232244 switched VS Code off the dead textmate/yaml.tmbundle to it),
4+
// so YAML's baseline is a maintained competitor — most of these it ALSO solves. Each id cites the
5+
// underlying upstream tracker; the canonical aggregator is RedCMD/YAML-Syntax-Highlighter#1
6+
// ("Pre-existing grammar issues"), which links the VS Code / textmate-yaml.tmbundle / atom-language-
7+
// yaml issues. The original report against Monogram was johnsoncodehk/monogram#12, since superseded
8+
// by RedCMD's organized #1. Shared by the README cross-language ✓ table (test/issue-table.ts).
9+
//
10+
// Each snippet is VALID YAML; the question is whether the grammar scopes the marked span correctly.
11+
// Same predicate shape as html-issue-cases.ts: {id, title, src, at, nth?, want:(scope)=>boolean}.
12+
// `want` asserts the GENUINELY-correct scope (verified by tokenizing both grammars), not "whatever
13+
// Monogram emits". Several are both-✓ (the maintained RedCMD grammar handles them too) — that is
14+
// honest and correct to show; #119 below is the lone Monogram-only ✓ in this set.
15+
16+
export interface YamlCase { id: string; title: string; src: string; at: string; nth?: number; want: (scope: string) => boolean; }
17+
18+
const isBlockScalar = (s: string) => s.includes('string.unquoted.block'); // | or > literal/folded body
19+
const isEscape = (s: string) => s.includes('constant.character.escape');
20+
const isAnchor = (s: string) => s.includes('anchor') && !!s.replace(/source\.yaml/g, '').match(/entity\.name|variable\.other/); // an anchor scope, not bare source
21+
const isMapKeyPunct = (s: string) => s.includes('punctuation.definition.map.key'); // the `?` explicit-key indicator
22+
const isDocMarker = (s: string) => s.includes('entity.other.document') || s.includes('keyword.control');
23+
const isTag = (s: string) => s.includes('entity.name.tag'); // scoped as a key NAME (entity.name.tag.yaml)
24+
const stringResolvesTo = (lang: string) => (s: string) => s.includes('string.unquoted') && s.includes(lang); // plain scalar is lexically a string that RESOLVES to a typed constant
25+
26+
export const cases: YamlCase[] = [
27+
// ── document structure ──
28+
{ id: 'vscode#170032', title: 'document markers `---` / `...` are document structure, not stray punctuation', src: '---\nfoo: bar\n...\n',
29+
at: '---', want: isDocMarker }, // both scope it entity.other.document.{begin,end}.yaml — Monogram derives the marker from the grammar, the official hand-rolls it (vscode#170032: frontmatter hyphens were unscoped)
30+
31+
// ── block scalars (`|` / `>`): the body is opaque string content, regardless of what it contains ──
32+
{ id: 'atom/language-yaml#114', title: 'a `#` in a block-scalar body is content, not a comment', src: '- >\n \n \n # detected\n',
33+
at: '# detected', want: isBlockScalar }, // the `# detected` line is inside the folded scalar → string.unquoted.block, NOT comment.line (atom#114: a hash sign breaks highlighting)
34+
{ id: 'tmbundle#38', title: 'a block scalar with leading/internal EMPTY lines stays one string region', src: '>\n\n folded\n line\n\n next\n * bullet\n',
35+
at: 'bullet', want: isBlockScalar }, // `* bullet` sits after an empty line, deeper-indented — it is block-scalar content, not a sequence item or a new scalar (tmbundle#38: text after `|`/`>` colorized differently when a leading newline exists). Checkpoint is `bullet` (downstream of the empty line — the hardest span); `folded` passes identically
36+
{ id: 'tmbundle#18', title: 'JSON-ish punctuation (`{`/`}`) and a tab indicator inside a block scalar stay content', src: 'block:\t|\n void main() {\n }\n',
37+
at: '}', want: isBlockScalar }, // the `}` is literal block-scalar content, not punctuation — and the indicator follows a TAB (tmbundle#18: `{ }` inside literal style breaks highlighting; #17 is the sibling colon case)
38+
39+
// ── explicit-key (`?`) constructs ──
40+
{ id: 'RedCMD/YAML-Syntax-Highlighter#1', title: 'an anchor `&a` in explicit-key (`?`) position is still an anchor', src: '? &a a\n: &b b\n: *a\n',
41+
at: '&a', want: isAnchor }, // `&a` after the explicit-key `?` is an anchor (entity.name.type.anchor / variable.other.anchor), not bare source — the original monogram#12 multi-item repro
42+
{ id: 'RedCMD/YAML-Syntax-Highlighter#1', title: 'a bare `?` opening an explicit multi-line sequence key is the map-key indicator', src: '?\n- a\n- b\n:\n- c\n- d\n',
43+
at: '?', want: isMapKeyPunct }, // the lone `?` (its value is the multi-line block sequence below) is punctuation.definition.map.key.yaml — both grammars handle the explicit complex-key form
44+
{ id: 'RedCMD/YAML-Syntax-Highlighter#1', title: 'a block scalar used AS an explicit key is scoped as a key name', src: '? |\n block key\n: v\n',
45+
at: 'block key', want: isTag }, // MONOGRAM-ONLY ✓: Monogram scopes the block-scalar explicit key as a key NAME (entity.name.tag.yaml); the official leaves it string.unquoted.block (it has no entity.name.tag) → only-Monogram
46+
47+
// ── quoted KEYS carry their escapes / plain scalars carry a string ancestor ──
48+
{ id: 'atom/language-yaml#119', title: 'an escape inside a double-quoted KEY is highlighted', src: '"foo\\nbar": 23\n',
49+
at: '\\n', want: isEscape }, // the `\n` inside the quoted key `"foo\nbar"` is constant.character.escape (atom#119: keys with escaped chars within are not highlighted correctly) — both grammars sub-scope escapes inside the key now
50+
{ id: 'tmbundle#39', title: 'a plain scalar resolving to `null` is lexically a string that resolves to a constant', src: 'a: null\n',
51+
at: 'null', want: stringResolvesTo('constant.language') }, // the FULL chain carries BOTH string.unquoted (it is lexically a plain scalar) AND constant.language (it resolves to null) — not constant.language alone (tmbundle#39: integers recognized as strings / vice versa; the string ancestor must survive)
52+
];

0 commit comments

Comments
 (0)