Skip to content

v4: move to TypeScript 7, framework support via content mappers - #103

Open
johnsoncodehk wants to merge 22 commits into
masterfrom
v4
Open

johnsoncodehk wants to merge 22 commits into
masterfrom
v4

Conversation

@johnsoncodehk

@johnsoncodehk johnsoncodehk commented Sep 2, 2026 •

Copy link
Copy Markdown
Owner

Summary

TSSLint v4 runs lint rules on TypeScript 7's checker (tsgo) and removes the Volar-based framework support. Framework files come back through TypeScript's own content mapper (tsconfig contentMappers) instead of per-framework CLI flags, so the tsconfig is the single source of truth. This PR is the first milestone: the CLI engine swap and the Volar removal.

Quick start

tsslint --project tsconfig.json   # no --vue-project etc., no typescript install needed for linting
// tsconfig.json (framework files, once content mapper support lands; see Remaining work)
{
  "contentMappers": [
    { "package": "@vue/content-mapper", "extensions": [".vue"], "options": { "target": 3.5 } }
  ]
}

Breaking changes

  • @tsslint/cli bundles its own engine. It depends on typescript-native-bridge (exact-pinned 6.0.3-bridge.16.tsgo.7.0.2) instead of a typescript peer dependency. Diagnostics come from the tsgo 7.0.2 checker and can differ from TS 6 in edge cases.
  • --vue-project, --vue-vine-project, --mdx-project, --astro-project and --ts-macro-project are removed, together with @volar/language-core, @volar/language-hub and @volar/typescript. Declare contentMappers in the tsconfig instead; there is no replacement flag.
  • Custom module resolvers and JS custom transformers are not honoured by the bridge, so remapping imports to other files on disk has no effect.
  • musl (Alpine) is not supported. The bridge links a glibc native binary. Node.js 22.6+ is unchanged.
  • Peak memory on checker-heavy runs is about 1.9× v3.
  • Existing caches are invalidated. The cache key no longer carries languages.

Remaining work

tsslint

  • Content mapper support: diagnostics mapped back to the original file through spanMap, fixes offered only on Exact / Verbatim spans.
  • Move from typescript-native-bridge to the TypeScript 7.1 API (typescript/async, typescript/ast), so TSSLint no longer depends on any TypeScript 5/6 API surface.
  • Rebuild @tsslint/typescript-plugin and the VS Code extension on the TypeScript 7 LSP extension points (LSP middleware, content mapper registration). Until then editor diagnostics for framework files keep the v3 setup.
Done

Engine

  • @tsslint/cli runs on typescript-native-bridge, a drop-in for the classic typescript API surface backed by tsgo; require.resolve('typescript') points at the bridge.

Volar removal

  • The five framework flags, the proxy language service and diagnostic mapping are removed (−1,497 lines), with packages/cli/lib/languagePlugins.ts and fixtures/meta-frameworks.
  • The languages plumbing left in the cache layer is removed.
  • Untracked tsgo / relay experiment artifacts are cleaned up.

Docs

  • docs/migration-v4.md: breaking changes, flag → contentMappers mapping, engine behavior, editor status and availability.
  • README requirements updated (bundled engine, no typescript pin, musl unsupported).

Tests

  • Full test suite passes, including the compat-eslint bench (107 rules × 33 files, clean parity) and upstream scope-manager (212/225, no regressions).
  • npm run lint on the repo itself: 59 passed.
  • Fixture output identical to the TS 6.0.3 baseline (no diagnostic drift on this corpus).

Notes

  • typescript-native-bridge needs Node ≥ 20.19, below the CLI's existing 22.6 requirement.
  • @tsslint/core typings are declared against stock typescript, so cross-package boundaries use type-only casts (ts as unknown as typeof import('typescript')); the runtime object is the same.
pnpm run build && npm run lint

References

- @tsslint/cli now depends on typescript-native-bridge (pinned
  6.0.3-bridge.15.tsgo.7.0.2) instead of the typescript peer dep;
  type checking runs on the TS 7 (tsgo) engine
- Remove --vue-project/--vue-vine-project/--mdx-project/--astro-project/
  --ts-macro-project flags and the Volar language plugin wiring
  (@volar/language-core, @volar/language-hub, @volar/typescript)
- Meta-framework support will be provided by the TS 7.1 content mapper
  (tsconfig contentMappers) once TNB rebases onto tsgo 7.1
- Remove fixtures/meta-frameworks and untracked tsgo/relay experiments
The file was deleted with the Volar wiring; the hardcoded corpus list
in packages/compat-eslint/test/bench/dogfood-corpus.ts still referenced
it, failing lazy-estree.test.js with ENOENT in CI.
…lumbing

- packages/cli now pins typescript-native-bridge 6.0.3-bridge.16.tsgo.7.0.2
- remove the languages parameter from the cache layer (dead since the Volar
  removal); the cache key no longer carries it, which also invalidates
  existing caches
- README: real framework-support section (tsconfig contentMappers), updated
  requirements (bundled engine, no typescript pin, musl unsupported)
- docs/migration-v4.md: breaking changes, flag -> contentMappers mapping,
  engine behavior notes, editor status, availability gates
TypeScript 7 has no tsserver plugin API, so @tsslint/typescript-plugin
and the VS Code extension built on it cannot work.
- Build the workspace with typescript@7.1.0-beta (module nodenext,
  Node >= 22.12 for require(esm)); drop typescript-native-bridge.
- @tsslint/types: SourceFile from typescript/ast, Program/Checker/
  DiagnosticCategory from typescript/sync. RuleContext gains `checker`
  (TS7 programs have no getTypeChecker); LinterContext is
  { typescript, getProgram }.
- @tsslint/core: lint() takes { program, checker, file } instead of a
  file name resolved through a LanguageService.
- Add tools/estree-oracle, a private test-only package pinning
  typescript@6 + typescript-eslint as the parity reference.
- Port core tests to real TS7 projects; drop the BuilderProgram POC.
tsl and tslint are built on the TypeScript 5/6 API and cannot run on
TS7. The eslint, ignore, category and diagnostics plugins now use
typescript/ast and typescript/sync.
- lazy-estree / tokens / ts-ast-scan read typescript/ast. TS7 shape
  differences are handled in lib/ts.ts helpers: postfixToken for ?/!,
  ModuleDeclaration.keyword, phaseModifier for type-only imports,
  heritage clauses parsed as TypeReference, synthesized (Reparsed)
  nodes, nameless array-binding holes, EndOfFile/JSDoc kinds.
- Scope analysis uses @typescript-eslint/scope-manager's analyze(),
  built lazily on first scopeManager read, replacing TsScopeManager.
- parserServices type queries go through the project checker.
- Parity tests compare against typescript-estree running on its own
  TypeScript 6 copy (tools/estree-oracle), bridged by node ranges.
  Tests depending on @typescript-eslint/eslint-plugin or the vendored
  scope-manager suite are removed: typescript-eslint cannot load TS7.
- dogfood: register all rules before the first file is linted, so the
  shared traversal sees every rule (previously only the first one ran).
- lint through typescript/sync snapshots; project file names come from the
  configured project so content-mapped files (e.g. .vue) are included
- replace the BuilderProgram incremental state with a program fingerprint
  (cache format v3)
- map diagnostics back to original files via SourceFile.spanMap; drop
  diagnostics that only cover generated code
- apply --fix only when every edit of a fix maps exactly
- render with the TS7 formatDiagnosticsWithColorAndContext
@johnsoncodehk johnsoncodehk changed the title v4: embed typescript-native-bridge, drop Volar-based framework support v4: move to TypeScript 7, framework support via content mappers Oct 22, 2026
@johnsoncodehk

Copy link
Copy Markdown
Owner Author

/release alpha 4.0.0

@github-actions

Copy link
Copy Markdown
Contributor

@johnsoncodehk

Copy link
Copy Markdown
Owner Author

/release alpha 4.0.0

@github-actions

Copy link
Copy Markdown
Contributor

@johnsoncodehk

Copy link
Copy Markdown
Owner Author

/release alpha 4.0.0

@github-actions

Copy link
Copy Markdown
Contributor

@johnsoncodehk

Copy link
Copy Markdown
Owner Author

/release alpha 4.0.0

@github-actions

Copy link
Copy Markdown
Contributor

johnsoncodehk and others added 8 commits October 22, 2026 19:46
Record the version published from f7d6918 by release run 39772141598, so the next `prerelease` release is 4.0.0-alpha.1. Only workspace specifiers change in pnpm-lock.yaml.
- Every package that loads `typescript/sync` / `typescript/ast` now depends
  on typescript 7.1.0-beta instead of peering on it. TSSLint's checker is
  its own engine; as a peer, pnpm resolved it to a host project's TS 6 and
  `require('typescript/sync')` failed.
- @tsslint/config and @tsslint/compat-eslint declare an optional eslint
  peer (>=9.39.0 <10.0.0). importESLintRules() loads core rules from
  `node_modules/eslint/lib/rules`, and both packages expose ESLint types.
- @tsslint/config peers on @tsslint/compat-eslint via `workspace:*`, so
  the published range always matches the release (it was `^3.0.0`).
…kind scans

ParenthesizedExpression, ParenthesizedType and ComputedPropertyName are
pass-through in convertChildInner: materialise returns the inner ESTree.
The scan relied on the child's target equalling the parent's to skip the
re-entry, but when the inner is a wrapper head (`(a?.b)` ->
ChainExpression, `(typeof import('x'))` -> TSTypeQuery) the parens level
expands the chain and leaves its tail as parentTarget, so the child's
visit entered the same chain again. In CPA and wildcard mode every
listener on those nodes fired twice (three times under `((a?.b))`), e.g.
no-non-null-asserted-optional-chain reported `(a?.b)!` twice.

The three kinds now join the structural pass-through list and recurse
with the parent's target unchanged.
…ession

typescript-estree treats a non-null assertion that ends an optional
chain (`a?.b!`, `a?.()!`, `a?.b.c!`, `a?.b!!`) as the chain's outermost
link and wraps it in ChainExpression. The lazy converter only wrapped
Member/CallExpression, so these produced a bare TSNonNullExpression and
rules keyed on `ChainExpression > TSNonNullExpression` - notably
no-non-null-asserted-optional-chain - silently missed them.

isOutermostOptionalChain now accepts NonNullExpression (the ts-ast-scan
ChainExpression predicate shares it), and TSNonNullExpression goes
through wrapChainIfNeeded like the other links.

Tests: lazy-estree-review-bugs compares 13 chain/non-null shapes with
typescript-estree, plus parent links, bottom-up materialise and dispatch
order for `a?.b!`; compat-pipeline runs the real
no-non-null-asserted-optional-chain rule and requires the same reports
as ESLint on typescript-eslint's parser. The estree-oracle test package
now carries @typescript-eslint/eslint-plugin for that.
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