Skip to content

Migrate to ESLint 10 flat config and TypeScript 6 with strict mode - #31

Merged
dduugg merged 2 commits into
mainfrom
migrate-eslint-flat-config-and-typescript-6
Sep 30, 2026
Merged

dduugg merged 2 commits into
mainfrom
migrate-eslint-flat-config-and-typescript-6

Conversation

@dduugg

@dduugg dduugg commented Sep 30, 2026

Copy link
Copy Markdown
Contributor

Handles the ESLint and TypeScript major versions that the grouped Dependabot PR #28 failed on. It has two commits, which can be reviewed separately.

TypeScript 5.9 → 6.0, with strict mode

This targets 6.0, not 7. typescript-eslint (latest 8.71.0) supports only typescript >=4.8.4 <6.1.0, and TypeScript 7's native port no longer exposes the compiler API it relies on. The range is pinned to ~6.0.3 so it stays inside that range.

TypeScript 6 changes several defaults, which tsconfig.json now handles:

  • types defaults to []: lists node and mocha explicitly so their globals (console, fs, describe, …) resolve.
  • strict defaults to true: sets it explicitly and fixes the 19 errors it reported. Also adds @types/proxyquire.
  • esModuleInterop defaults to true, and setting it to false is deprecated. Namespace imports now compile to __importStar(require(...)), which returns a plain object. That broke import * as pq from 'proxyquire' at runtime (TypeError: pq is not a function), so it's now a default import. The other namespace imports (vscode, fs, path, …) are all used as objects and are unaffected.

I compared the emitted JavaScript against main's TypeScript 5.9 build. Apart from the interop helpers, the only runtime changes are these:

  • ruby.packwerk with no active editor now returns early. Before, it threw a TypeError that VS Code reported as a command error. This is the one real bug strict mode found.
  • parse() returns null instead of undefined when it catches a non-SyntaxError. Its only caller treats the two the same.
  • Task.run() returns a resolved promise instead of undefined for a cancelled task. Its only caller awaits it.
  • The task error log passes the error object itself, which prints the message and stack once. Before, it logged the message and then the stack, which repeats the message.

ESLint 8 → 10, flat config

ESLint 10 removed .eslintrc support, so .eslintrc.json becomes eslint.config.mjs. @typescript-eslint/eslint-plugin and @typescript-eslint/parser are replaced by the typescript-eslint package, which bundles both.

  • Same rules: I compared ESLint 8's and ESLint 10's --print-config output on a source file and a test file. The rule names, severities and options match, except for @typescript-eslint/no-var-requires. It's deprecated and fully covered by @typescript-eslint/no-require-imports, which is already enabled.
  • Formatting rules kept: the deprecated core formatting rules (max-len, quotes, semi, no-trailing-spaces, no-multiple-empty-lines) still work in v10 and are unchanged. Moving them to @stylistic/eslint-plugin can be a follow-up.
  • New default warning: ESLint 9+ warns about unused eslint-disable directives. The codebase has none.
  • Packaging: eslint.config.mjs is added to .vscodeignore.

Testing

  • yarn install --frozen-lockfile, yarn run vscode:prepublish and yarn lint pass with no errors or warnings. All 13 tests pass locally.
  • I linted a scratch file with planted violations. no-var, quotes, no-require-imports, semi, eqeqeq, curly, no-console, radix and the unused-directive check all fired.
  • yarn audit shows only the existing low-severity diff 7.0.0 advisory, which Dependabot auto-dismissed (#40).

Not in this PR

  • The rest of Bump the npm group with 11 updates #28: chai 6, sinon 22, @types/sinon, @types/node 26 and @types/vscode 1.138.
  • TypeScript 7 in Dependabot: until typescript-eslint supports it, Dependabot will keep proposing TS 7. Ignoring TypeScript majors in .github/dependabot.yml would stop that.

TypeScript 7 is out, but typescript-eslint supports only >=4.8.4 <6.1.0,
and TS 7's native port no longer exposes the compiler API it relies on.
6.0 is the latest release that lint can use.

TypeScript 6 changes several defaults:

- types defaults to [], so list node and mocha explicitly for their
  globals (console, fs, describe, ...).
- strict defaults to true. Set it explicitly and fix the 19 errors it
  reports, adding @types/proxyquire for the one untyped import.
- esModuleInterop defaults to true, and setting it to false is
  deprecated. Namespace imports now go through __importStar, which
  returns a plain object, so the proxyquire namespace import was no
  longer callable ("pq is not a function"). Import it as a default
  import instead.

Strict mode surfaced one real bug: running the ruby.packwerk command
with no active editor threw a TypeError. It now returns early. Other
fixes are type-only or equivalent for their callers: parse() returns
null rather than undefined for a non-SyntaxError, and Task.run()
returns a resolved promise for a cancelled task. The task error log now
passes the error itself, rather than logging its message twice.
ESLint 9 made flat config the default and ESLint 10 removed .eslintrc
support entirely, so port .eslintrc.json to eslint.config.mjs. Replace
@typescript-eslint/eslint-plugin and @typescript-eslint/parser with the
typescript-eslint package, which bundles both.

The resolved rule set matches the old config (compared via
--print-config on a source and a test file), except for
@typescript-eslint/no-var-requires. It is deprecated and fully covered
by @typescript-eslint/no-require-imports, which was already enabled.
The deprecated core formatting rules (max-len, quotes, semi, and so on)
still work in v10 and are kept as-is.

ESLint 9+ also reports unused eslint-disable directives as warnings by
default; the codebase has none.
@dduugg
dduugg requested a review from a team as a code owner September 30, 2026 18:10
@dduugg
dduugg merged commit 90be961 into main Sep 30, 2026
7 checks passed
@dduugg
dduugg deleted the migrate-eslint-flat-config-and-typescript-6 branch September 30, 2026 18:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

1 participant