feat(vscode): derive the Vue TextMate grammars with Monogram#6085
Draft
johnsoncodehk wants to merge 14 commits into
Draft
feat(vscode): derive the Vue TextMate grammars with Monogram#6085johnsoncodehk wants to merge 14 commits into
johnsoncodehk wants to merge 14 commits into
Conversation
…guage.json Pure rename + package.json path repoint; the file contents are unchanged from the hand-written grammars. Split out from the content swap so the rename is tracked cleanly in history (the next commit replaces the contents with Monogram's output).
Replace the hand-written Vue syntax grammars with ones derived by Monogram from a single proven Vue/HTML grammar. The main SFC grammar and the two injections (directives + interpolations) are now generated output. The injections keep the same scopeNames (vue.directives / vue.interpolations), selectors, and injectTo set, and remain thin stubs that include text.html.vue#vue-directives / #vue-interpolations (the rule bodies live in the main grammar's repository) — the same topology as before. The <template> body now embeds text.html.derivative (the embedded-HTML-fragment scope VS Code's HTML extension provides), which is what the interpolation injection targets. Notable behaviour change: an `as` type assertion in a directive value (#5012 / #6007 / #2096 / #520) no longer eats the closing quote — the derived grammar bounds the value with a capture-embed, so `:msg="msg as string"` recovers correctly after the cast instead of leaking TypeScript across the rest of the tag. Grammar snapshot regenerated (extensions/vscode/tests/grammar.spec.ts).
…ammar snapshots Load the real text.html.derivative, source.tsx and source.js.jsx grammars in the embedded grammar snapshot harness so that the <template> body and <script lang="tsx|jsx"> paths are actually exercised. Previously only text.html.basic and plain TS/JS were loaded, so the begin/while SFC block rules silently fell back to #tag and these paths went untested. Add fixtures for interpolation inside an element, text-only interpolation, and JSX/TSX script blocks.
Pin down that static style="..." / style='...' embed source.css and that
bound :style="{ ... }" embeds source.ts, now that the harness loads the real
text.html.derivative + css grammars.
Mirror update-html-data: a scheduled (+ manual) workflow that pulls the generated vue.tmLanguage.json / vue.directives / vue.interpolations from johnsoncodehk/monogram, runs the formatter, regenerates the grammar snapshot, and commits any change.
Ari4ka
approved these changes
Jun 13, 2026
…Monogram submodule Vendor johnsoncodehk/monogram as a git submodule and move its vue.ts grammar definition in as vue.monogram.ts. The Vue TextMate grammars AND the VS Code language configuration are now generated locally by scripts/generate-grammar.ts (engine from the submodule; the generation path has no external deps), replacing the daily curl of pre-built JSON from monogram master. - `npm run gen:grammar` regenerates and formats the artifacts. - A grammar.spec.ts guard fails if the committed output drifts from vue.monogram.ts + the pinned submodule. - sync-grammar.yml now bumps the submodule and regenerates. The language configuration is now derived (Monogram gained markup language-config support — tag-aware onEnter / indentation, region folding, brackets), at parity with the hand-written one. The grammar is regenerated from monogram master: the SFC raw-text close rules are restructured and block close tags lowercased; the only tokenization change is template-in-template.vue (a plain </template> close no longer leaks text.pug).
…source test.yml now checks out the monogram submodule (the grammar tests import it), and dprint-formats vue.monogram.ts + generate-grammar.ts to the repo style.
Bumps the monogram submodule: generateTmLanguage / generateMarkupInjection now read grammar.name (its single source of truth) instead of taking it as an argument. Generated output is unchanged.
monogram is dropping its Vue grammar definition + tests, so its Vue correctness
gates move here, run against the generated grammar:
- directives + {{ }} interpolation injection
- embed boundaries (#1666 </script>, #5012 as-cast, #3999 multi-line start tag)
- interpolation expression scoping (statements suppressed, operators kept, nested re-enters)
- <style lang="X"> dialect embedding at every structural position
- the reported-issue regression corpus (vue-issue-cases)
A tokenize/scopeLookup harness (tests/vueGrammarHarness.ts) runs the generated
grammar + the real VS Code embedded grammars through vscode-tmlanguage-snapshot
(the same engine as grammar.spec.ts). `test:grammar` now also runs these gates,
so a submodule bump that breaks tokenization fails the sync workflow.
The embedded grammars are downloaded (not committed); without awaiting the sync the harness could read them before grammar.spec.ts downloaded them, racing to ENOENT in CI. Mirror grammar.spec.ts and await the sync first.
…a submodule
Replaces the extensions/vscode/monogram git submodule with a pinned github
devDependency ("monogram": "github:johnsoncodehk/monogram#<hash>") — simpler to
install (pnpm, no submodules: true in CI, no .gitmodules).
vue.monogram.ts / generate-grammar.ts now import `monogram/…` (the package).
Node refuses to type-strip `.ts` under node_modules, so `gen:grammar` runs via
`node --import scripts/strip-node-modules-types.mjs` (an in-thread load hook that
strips Monogram's `.ts`); vitest handles them already. Generated output is
byte-identical.
sync-grammar.yml now bumps the pinned commit in package.json + pnpm install,
replacing the submodule update.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Draft / proposal — for evaluation. Replaces the hand-written Vue TextMate grammars and the VS Code language configuration with output generated by Monogram, pinned here as a github dependency.
Architecture
"monogram": "github:johnsoncodehk/monogram#<commit>"inextensions/vscode/package.json). Its engine (gen-tm,gen-vscode-config) and the reused HTML base (html.ts) come from the dependency; the Vue grammar definition lives in this repo asvue.monogram.ts.scripts/generate-grammar.tsproduces, from that one source, the three TextMate grammars (vue.tmLanguage.json+ thevue.directives/vue.interpolationsinjections) and the language configuration (languages/vue-language-configuration.json). It needs only Monogram's own source — no other deps.npm run gen:grammarregenerates + formats; agrammar.spec.tsguard fails if the committed output drifts fromvue.monogram.ts+ the pinned commit. (Generation runs vianode --import scripts/strip-node-modules-types.mjs, a small load hook, because Node won't type-strip.tsundernode_modules; the tests run under vitest, which handles them directly.)Language configuration is now generated
Monogram gained markup language-config support: for a grammar with
markup,generateLanguageConfigderives the tag-awareonEnterRules/indentationRules, region folding from the comment delimiters, comment + embedded-language brackets, and the word pattern — all from the markup data (void / raw-text tag sets, comment delimiters, embed scopes), gated so the token-stream grammars (TS / JS / YAML) are unaffected and theagnosticgate still passes. The result is at parity with the hand-writtenvue-language-configuration.json.Test coverage
Monogram's Vue correctness gates move here, run against the generated grammar (
extensions/vscode/tests/vue-highlight.spec.ts, on the samevscode-tmlanguage-snapshotengine + the real VS Code embedded grammars): directive /{{ }}injection, embed boundaries (#1666</script>, #5012as-cast, #3999 multi-line start tag), interpolation expression scoping,<style lang="X">dialect embedding, and the reported-issue regression corpus below.npm run test:grammarruns these gates alongside the snapshot, so bumping the pinned monogram commit and breaking tokenization fails the sync workflow.Reported-issue coverage
The regression corpus verifies the generated grammar against issues filed on the hand-written Vue grammar — it improves on 5 and matches on 18.
astype assertion in directive valueconst a = 1;</script>(content on the close line) embeds + clean close</script>, then a second<script setup>blockas constcast in a v-for valueascast followed by another attribute… and 18 more both the generated and hand-written grammar handle (✓ / ✓)
instanceofin {{ }}typeof x !==in v-if?./??in {{ }}=>in {{ }}<operator in {{ }} (not a tag!)export typebefore</script><script lang="ts">start tag keeps the body as thetsfamilytemplate{{inside a<script>string<style>block<script lang="tsx">body embeds the declaredsource.tsx<script lang="jsx">body embeds the declaredsource.js.jsxgeneric="T extends U">type-param list embeds as TS:[attr].propmodifier shorthandtypeas a v-for loop variableKeeping in sync with Monogram
.github/workflows/sync-grammar.yml(daily +workflow_dispatch) bumps the pinnedmonogramcommit inpackage.jsonto monogrammaster, runspnpm install, regenerates, and commits any change — a reviewable, reproducible pin (recorded inpnpm-lock.yaml), replacing the old curl of pre-built JSON.Behaviour
Regenerated from monogram
master: the SFC raw-text close rules are restructured and the block close tags lowercased. The only tokenization change in the snapshot istemplate-in-template.vue, where a plain</template>close no longer leakstext.pugand now matches every other close tag. The grammar snapshot and the language-config drift guard are the best place to review the concrete impact.