docs: state the code-language comments as current contracts
The new JSDoc and test prose narrated the change instead of the shipped behavior: "pre-unification", "the old read table", "used to persist ... it now persists". A reader at HEAD cannot resolve those stages, and the persisted-value rule they describe is a durability contract that stands on its own. Restate each as the present contract: a suffix whose value a recorded session already holds keeps it byte-identical; every other suffix takes its language's short name. Rename the test's transcribed expectation away from the branch vantage and re-wrap the module comment the earlier edit left over-long.
This commit is contained in:
1 parent
5774f385f4
commit
88b1ea3c0b
6 files changed
+58
-55
No files matched your search
@@ -11,10 +11,11 @@
|
||||
* (`@deepseek-ai/dsh-util-code-language`: python, rust, yaml, markup, …) is
|
||||
* imported lazily and registered the first time such a language is requested,
|
||||
* so a session that never opens a code surface in one of those languages pays
|
||||
* neither the grammar modules nor their synchronous init. The first render of a lazy language falls back to plain text while its
|
||||
* grammar loads, then {@link onGrammarLoaded} notifies subscribers to re-render
|
||||
* with highlighting. An unknown or absent language falls back to plain text (no
|
||||
* highlighting, still monospace) — never an error.
|
||||
* neither the grammar modules nor their synchronous init. The first render of a
|
||||
* lazy language falls back to plain text while its grammar loads, then
|
||||
* {@link onGrammarLoaded} notifies subscribers to re-render with highlighting.
|
||||
* An unknown or absent language falls back to plain text (no highlighting, still
|
||||
* monospace) — never an error.
|
||||
*/
|
||||
|
||||
import { createHighlighterCoreSync, createCssVariablesTheme } from 'shiki/core'
|
||||
@@ -233,7 +234,7 @@ const LANG_ALIASES = new Map<string, string>([
|
||||
|
||||
/**
|
||||
* Resolve a language hint to the grammar id {@link LANG_ALIASES} selects.
|
||||
* @param lang - Language hint from a code surface: a shared canonical id or a legacy read-card hint.
|
||||
* @param lang - Language hint from a code surface: a canonical grammar id or the read card's persisted short id.
|
||||
* @returns The resolved grammar id, or `undefined` when the table aliases no grammar.
|
||||
*/
|
||||
export function grammarForHint(lang: string | undefined): string | undefined {
|
||||
|
||||
@@ -51,12 +51,12 @@ describe('code highlighting', () => {
|
||||
expect(languageForPath).toBe(sharedLanguageForPath)
|
||||
})
|
||||
|
||||
it('reaches one grammar from the shared canonical id and the read card legacy hint', () => {
|
||||
// The read card persists the old short ids while this surface reads the
|
||||
// canonical table directly; both must select the same grammar, so a
|
||||
// persisted hint can never render differently from this surface's own
|
||||
// selection. This is the alias-normalized check the identity assertion
|
||||
// cannot make now that the read projection intentionally differs.
|
||||
it("reaches one grammar from the shared canonical id and the read card's persisted hint", () => {
|
||||
// The read card persists short ids while this surface reads the canonical
|
||||
// table directly; both must select the same grammar, so a persisted hint can
|
||||
// never render differently from this surface's own selection. The identity
|
||||
// assertion above cannot cover that, because the two projections
|
||||
// intentionally differ.
|
||||
for (const extension of CODE_HIGHLIGHT_EXTENSIONS) {
|
||||
const path = `file.${extension}`
|
||||
const readHint = readLangHintForPath(path)
|
||||
|
||||
@@ -173,10 +173,9 @@ ${body}
|
||||
* Derive the persisted `lang` hint from a read path's file extension. The shared
|
||||
* table in `@deepseek-ai/dsh-util-code-language` owns the recognized suffixes and
|
||||
* the path rules (both separators, a leading dot as the extension separator, and
|
||||
* prototype-key safety); `readLangHintForPath` projects the read card's
|
||||
* historical short ids over it, so a suffix the pre-unification read table
|
||||
* recognized keeps its byte-identical old value while a newly added suffix uses
|
||||
* its language's short name.
|
||||
* prototype-key safety); `readLangHintForPath` projects the read card's short ids
|
||||
* over it, so a suffix whose value a recorded session already holds keeps it
|
||||
* byte-identical while every other suffix uses its language's short name.
|
||||
* @param path - the model-facing path the read reported.
|
||||
* @returns the persisted language hint, or `undefined` when the extension maps to none.
|
||||
*/
|
||||
|
||||
@@ -119,11 +119,12 @@ describe('buildWindow', () => {
|
||||
})
|
||||
|
||||
/**
|
||||
* The pre-unification read table's extension-to-hint mapping, transcribed
|
||||
* verbatim. A read card persisted one of these strings, so changing any value
|
||||
* changes replay-visible output for an already-recorded session.
|
||||
* The short `lang` values a recorded read persisted, transcribed verbatim.
|
||||
* Changing any value changes replay-visible output for an already-recorded
|
||||
* session, so this table is the test's independent expectation rather than a
|
||||
* second read of the implementation's own data.
|
||||
*/
|
||||
const MASTER_READ_LANG_BY_EXTENSION: Readonly<Record<string, string>> = {
|
||||
const PERSISTED_READ_LANG_BY_EXTENSION: Readonly<Record<string, string>> = {
|
||||
ts: 'ts', tsx: 'tsx', mts: 'ts', cts: 'ts',
|
||||
js: 'js', jsx: 'jsx', mjs: 'js', cjs: 'js',
|
||||
json: 'json', jsonc: 'json',
|
||||
@@ -138,16 +139,16 @@ const MASTER_READ_LANG_BY_EXTENSION: Readonly<Record<string, string>> = {
|
||||
}
|
||||
|
||||
describe('langFromPath', () => {
|
||||
it('keeps every pre-unification suffix byte-identical to the old persisted hint', () => {
|
||||
expect(Object.keys(MASTER_READ_LANG_BY_EXTENSION)).toHaveLength(43)
|
||||
for (const [extension, hint] of Object.entries(MASTER_READ_LANG_BY_EXTENSION)) {
|
||||
it('keeps every already-persisted suffix byte-identical', () => {
|
||||
expect(Object.keys(PERSISTED_READ_LANG_BY_EXTENSION)).toHaveLength(43)
|
||||
for (const [extension, hint] of Object.entries(PERSISTED_READ_LANG_BY_EXTENSION)) {
|
||||
expect(langFromPath(`file.${extension}`), extension).toBe(hint)
|
||||
}
|
||||
})
|
||||
|
||||
it('gives a suffix added after the old read table the language short id', () => {
|
||||
// A suffix the old read table did not know used to persist the canonical
|
||||
// grammar id; it now persists the language's short name.
|
||||
it('gives every other suffix the language short id', () => {
|
||||
// A suffix with no persisted value has none to preserve, so the projection
|
||||
// applies the language's short name instead of the canonical grammar id.
|
||||
expect(langFromPath('build.ps1')).toBe('ps1')
|
||||
expect(langFromPath('deploy.bat')).toBe('bat')
|
||||
expect(langFromPath('.env')).toBe('env')
|
||||
@@ -167,7 +168,7 @@ describe('langFromPath', () => {
|
||||
expect(langFromPath('notebook.ipynb')).toBe('json')
|
||||
})
|
||||
|
||||
it('maps a known extension to its old short hint, case-insensitively', () => {
|
||||
it('maps a known extension to its persisted short hint, case-insensitively', () => {
|
||||
expect(langFromPath('src/a.ts')).toBe('ts')
|
||||
expect(langFromPath('src/a.TSX')).toBe('tsx')
|
||||
expect(langFromPath('/abs/module.mjs')).toBe('js')
|
||||
@@ -177,7 +178,8 @@ describe('langFromPath', () => {
|
||||
|
||||
it('keeps the canonical ids the Client code surfaces use out of the persisted hint', () => {
|
||||
// The Client reads the shared table directly; only this projection owns the
|
||||
// persisted value, so the two intentionally differ for the old suffixes.
|
||||
// persisted value, so the two intentionally differ for the suffixes a
|
||||
// recorded session already holds.
|
||||
expect(languageForPath('src/a.ts')).toBe('typescript')
|
||||
expect(languageForPath('README.md')).toBe('markdown')
|
||||
expect(langFromPath('src/a.ts')).not.toBe(languageForPath('src/a.ts'))
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
* highlighter's alias table resolves, so a language returned here reaches
|
||||
* `highlightLines`/`highlightToHtml` unchanged; a filename outside the table, or
|
||||
* one naming a language the highlighter does not register, renders as plain text.
|
||||
* The read card predates the shared table, so {@link readLangHintForPath} projects
|
||||
* short ids over this table: the suffixes the old read table already recognized
|
||||
* keep their historical values byte-identical, and every later suffix persists the
|
||||
* The read tool persists a short `lang` id, so {@link readLangHintForPath}
|
||||
* projects short ids over this table: a suffix whose value a recorded session
|
||||
* already holds keeps it byte-identical, and every other suffix persists the
|
||||
* canonical language's short name rather than its grammar id.
|
||||
* @module @deepseek-ai/dsh-util-code-language
|
||||
*/
|
||||
@@ -101,10 +101,10 @@ const LANGUAGES = new Map(Object.entries(LANGUAGE_EXTENSIONS)
|
||||
export const CODE_HIGHLIGHT_EXTENSIONS: readonly string[] = [...LANGUAGES.keys()]
|
||||
|
||||
/**
|
||||
* The read card's historical short language hints for the suffixes its
|
||||
* pre-unification table recognized. Keys are shared-table extensions, so the
|
||||
* recognized set still derives from {@link LANGUAGE_EXTENSIONS}; values preserve
|
||||
* the old persisted `lang` strings byte for byte. An extension absent here but
|
||||
* The short `lang` values recorded sessions already hold, keyed by the suffix
|
||||
* that produced them. Keys are shared-table extensions, so the recognized set
|
||||
* derives from {@link LANGUAGE_EXTENSIONS}; each value is the exact string a
|
||||
* recorded session persists and must not change. An extension absent here but
|
||||
* present in the shared table falls back to {@link SHORT_BY_LANGUAGE}, so every
|
||||
* persisted hint is a short id.
|
||||
*/
|
||||
@@ -123,13 +123,13 @@ const READ_LANG_BY_EXTENSION = new Map<string, string>(Object.entries({
|
||||
}))
|
||||
|
||||
/**
|
||||
* The short `lang` id the read card persists for each canonical language the
|
||||
* pre-unification read table did not recognize. Keys are exactly the languages
|
||||
* in {@link LANGUAGE_EXTENSIONS}: the 27 languages the old read table covered
|
||||
* repeat their historical short id, and the remaining 33 shorten the grammar id
|
||||
* (`powershell` to `ps1`, `hcl` to `tf`, `system-verilog` to `sv`).
|
||||
* Keys are canonical ids, not untrusted extensions, so an object lookup cannot
|
||||
* reach an `Object.prototype` member the way the extension tables can.
|
||||
* The short `lang` id the read card persists, keyed by canonical language id.
|
||||
* Keys are exactly the languages in {@link LANGUAGE_EXTENSIONS}: where
|
||||
* {@link READ_LANG_BY_EXTENSION} does not already fix a suffix's value, this
|
||||
* table supplies the language's short name (`powershell` to `ps1`, `hcl` to
|
||||
* `tf`, `system-verilog` to `sv`) rather than the grammar id. Keys are canonical
|
||||
* ids, not untrusted extensions, so an object lookup cannot reach an
|
||||
* `Object.prototype` member the way the extension tables can.
|
||||
*/
|
||||
const SHORT_BY_LANGUAGE: Readonly<Record<string, string>> = {
|
||||
typescript: 'ts',
|
||||
@@ -218,11 +218,11 @@ export function languageForPath(path: string): string | undefined {
|
||||
|
||||
/**
|
||||
* Derive the Host read card's persisted `lang` hint from a read path's
|
||||
* extension. The hint is always a short id: a suffix the pre-unification read
|
||||
* table recognized keeps its old value (`ts`, `md`, `cpp`, …), while a suffix
|
||||
* only the shared table knows (`.ps1`, `.env`, `.tf`, …) uses its language's
|
||||
* short name (`ps1`, `env`, `tf`, …) rather than the canonical grammar id. An
|
||||
* unrecognized suffix stays `undefined`, so the card renders as plain text.
|
||||
* extension. The hint is always a short id: a suffix a recorded session already
|
||||
* holds keeps its persisted value (`ts`, `md`, `cpp`, …), while a suffix with no
|
||||
* recorded value (`.ps1`, `.env`, `.tf`, …) uses its language's short name
|
||||
* (`ps1`, `env`, `tf`, …) rather than the canonical grammar id. An unrecognized
|
||||
* suffix stays `undefined`, so the card renders as plain text.
|
||||
* @param path - the model-facing path the read reported.
|
||||
* @returns the persisted language hint, or `undefined` when the extension maps to none.
|
||||
*/
|
||||
|
||||
@@ -32,7 +32,7 @@ describe('languageForPath', () => {
|
||||
expect(languageForPath(path)).toBe(language)
|
||||
})
|
||||
|
||||
it('keeps the extensions the two former tables already agreed on', () => {
|
||||
it('resolves the extensions the preview and read tables already agreed on', () => {
|
||||
expect(languageForPath('src/a.ts')).toBe('typescript')
|
||||
expect(languageForPath('src/a.TSX')).toBe('typescript')
|
||||
expect(languageForPath('/abs/module.mjs')).toBe('javascript')
|
||||
@@ -41,7 +41,7 @@ describe('languageForPath', () => {
|
||||
expect(languageForPath('C:\\src\\main.rs')).toBe('rust')
|
||||
})
|
||||
|
||||
it('resolves every extension that only the preview table knew, including the new ones', () => {
|
||||
it('resolves every suffix the preview registry declares beyond the persisted read hints', () => {
|
||||
for (const extension of ['jsonl', 'ndjson', 'pyw', 'pyi', 'rake', 'gemspec', 'hh', 'hxx', 'kts', 'xhtml', 'xsd', 'xsl', 'xslt']) {
|
||||
expect(languageForPath(`file.${extension}`), extension).toBeDefined()
|
||||
}
|
||||
@@ -91,7 +91,7 @@ describe('languageForPath', () => {
|
||||
})
|
||||
|
||||
describe('readLangHintForPath', () => {
|
||||
it('keeps the read card pre-unification short hints for the old suffixes', () => {
|
||||
it('keeps the read card short hints recorded sessions already hold', () => {
|
||||
expect(readLangHintForPath('src/a.ts')).toBe('ts')
|
||||
expect(readLangHintForPath('src/a.tsx')).toBe('tsx')
|
||||
expect(readLangHintForPath('src/a.mts')).toBe('ts')
|
||||
@@ -110,7 +110,7 @@ describe('readLangHintForPath', () => {
|
||||
expect(readLangHintForPath('page.htm')).toBe('html')
|
||||
})
|
||||
|
||||
it('uses the language short id for a suffix added after the old read table', () => {
|
||||
it('uses the language short id for a suffix with no persisted value', () => {
|
||||
expect(readLangHintForPath('build.ps1')).toBe('ps1')
|
||||
expect(readLangHintForPath('.env')).toBe('env')
|
||||
expect(readLangHintForPath('infra.tf')).toBe('tf')
|
||||
@@ -137,11 +137,12 @@ describe('readLangHintForPath', () => {
|
||||
}
|
||||
|
||||
it('persists a short id for every suffix, never a canonical grammar id', () => {
|
||||
// The persisted `lang` has one style: the historical short id for the
|
||||
// suffixes the old read table knew, the language's short name otherwise. A
|
||||
// result outside both sets is a canonical grammar id leaking back in
|
||||
// (`powershell`, `dotenv`, `latex`, `hcl`). The old table's `tsx`/`jsx`
|
||||
// hints are the only legacy values absent from the short-name table.
|
||||
// The persisted `lang` has one style: a short id. Every suffix must resolve
|
||||
// to one of these short names, except the persisted `tsx`/`jsx` values,
|
||||
// which the language table cannot express because typescript and javascript
|
||||
// already shorten to `ts` and `js`. A result outside both sets is a
|
||||
// canonical grammar id leaking back in (`powershell`, `dotenv`, `latex`,
|
||||
// `hcl`).
|
||||
const allowed = new Set([...Object.values(SHORT_BY_LANGUAGE), 'tsx', 'jsx'])
|
||||
for (const extension of CODE_HIGHLIGHT_EXTENSIONS) {
|
||||
const hint = readLangHintForPath(`file.${extension}`)
|
||||
|
||||
Reference in new issue
Block a user