Skip to content

feat(syntax-language): own the extension-to-grammar map - #133

Merged
tmccoy14 merged 1 commit into
mainfrom
feat/syntax-language-map
Sep 11, 2026
Merged

feat(syntax-language): own the extension-to-grammar map#133
tmccoy14 merged 1 commit into
mainfrom
feat/syntax-language-map

Conversation

@tmccoy14

@tmccoy14 tmccoy14 commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

CodeBlock highlights through microlighter, so which grammar handles which file is a fact about this package's own dependency. Guardian Studio and Explorer each kept a byte-identical copy of that map, and each declared microlighter as a devDependency purely to test it — reaching past equality to inspect its transitive dependency, with independently pinned ranges that could resolve to a version equality does not ship. Copilot flagged that as critical on eqtylab/explorer#62.

Owning the map here lets both consumers drop that devDependency.

Surface

New subpath, @eqtylab/equality/syntax-language:

GRAMMAR_BY_EXTENSION: Record<string, string>   // 55 entries
GRAMMAR_BY_FILENAME:  Record<string, string>   // 5 entries
grammarForFilename(fileName): string | undefined
hasFileExtension(fileName): boolean

Deliberately not re-exported from the root barrel. It is data, not a component, and reaching it through the barrel pulls every CSS module into a plain Node consumer — which fails outright in a test runner with Unknown file extension ".css". The subpath bundle has no imports at all.

This is purely additive: nothing importing from the root barrel changes.

Only what this release ships

The map names only grammars microlighter 2.1.0 actually ships, so it describes what a file will really look like. An earlier draft pre-named grammars that exist upstream but are unreleased (astro, ini, nginx, nix, ron); that was dropped, because a .ini file rendering plain while the map says ini: 'ini' reads as a bug to anyone debugging it.

Nothing is lost by waiting: the check below reports a newly shipped grammar as unreachable and names it, so the build tells you what to add — and the extension stays a deliberate choice (.cfg → ini is a judgement call, not a given).

Why a build check instead of tests

Nothing upstream can generate this map. The grammar modules carry no file-type metadata (0 of 37 declare fileTypes), and microlighter's own alias list covers markdown fence tags rather than filenames — it has nothing for cc, hpp, h, m, ps1, patch, and nothing for extensionless names like Dockerfile. Some entries are also editorial: xml → html is a substitute, since the HTML grammar already describes its tag-and-attribute shape.

So the data stays hand-written, and what gets automated is keeping it honest. src/scripts/check-grammars.js runs inside build:lib, which means code-quality.yaml gates it on every PR and publish.yaml gates it on every release — a drifting map fails the build rather than shipping code blocks that silently render plain.

Because nothing is pre-named, the invariant is now an exact correspondence in both directions:

Checked 37 mapped grammars against 37 shipped

and when it drifts:

- Maps onto 1 grammar(s) the highlighter does not ship: notagrammar.
    Either the name is wrong, or the grammar is not in this release yet and the entry should wait.
- The highlighter ships 1 grammar(s) no filename reaches: sql.
    Add an extension for each, or list it in NOT_FILE_SHAPED if no filename would select it.

Verification

  • check:grammars passes against 2.1.0 — 37 mapped, 37 shipped, exact correspondence
  • Confirmed it exits 1 on both failure directions
  • build:lib succeeds; dist/lib/syntax-language.{js,cjs,d.ts} emitted and the subpath resolves with no imports
  • prettier and eslint clean

Consumers are ready and waiting on this release: they move to the subpath, delete their copies, and drop the microlighter devDependency. Verified locally against this build — Explorer 36 tests green across 3 builds, Studio CI green at 138 tests.

🤖 Generated with Claude Code

CodeBlock highlights through microlighter, so which grammar handles which
file is a fact about this package's own dependency. Guardian Studio and
Explorer each kept a byte-identical copy of that map, and each declared
microlighter as a devDependency purely to test it — reaching past equality
to inspect its transitive dependency, with independently pinned ranges
that could resolve to a version equality does not ship.

Owning the map here lets both drop that devDependency.

Exposed at '@eqtylab/equality/syntax-language' rather than from the root
barrel: it is data, not a component, and reaching it through the barrel
pulls every CSS module into a plain Node consumer, which breaks a test
runner outside a browser environment. The subpath bundle has no imports.

Only grammars the current release ships are named, so the map describes
what a file will actually look like rather than what it might look like
after some future upgrade. check-grammars.js enforces that exactly, in
both directions: no mapped grammar the highlighter does not ship, and no
shipped grammar no filename reaches. It runs inside build:lib, so a
drifting map fails the build and the release rather than shipping code
blocks that render plain.

The map stays hand-written because nothing upstream can generate it: the
grammar modules carry no file-type metadata and the highlighter's own
alias list covers fence tags rather than filenames. When upstream adds a
grammar, the unreachable check names it and the extension is added then.

Additive, so nothing that imports from the root barrel changes.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@tmccoy14
tmccoy14 force-pushed the feat/syntax-language-map branch from 3908ca7 to f9c2a17 Compare September 11, 2026 13:30
@tmccoy14 tmccoy14 self-assigned this Sep 11, 2026
@tmccoy14
tmccoy14 merged commit ce8ddc0 into main Sep 11, 2026
1 check passed
@tmccoy14
tmccoy14 deleted the feat/syntax-language-map branch September 11, 2026 13:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant