Skip to content

fix(typescript): respect unified js/ts.* settings - #129

Open
NamesMT wants to merge 1 commit into
volarjs:masterfrom
NamesMT:feat/unified-js-ts-settings
Open

NamesMT wants to merge 1 commit into
volarjs:masterfrom
NamesMT:feat/unified-js-ts-settings

Conversation

@NamesMT

@NamesMT NamesMT commented Oct 4, 2026 •

Copy link
Copy Markdown

Problem

The TypeScript service only read the deprecated per-language settings (typescript.* / javascript.*), so an embedded <script> block ignored the unified js/ts.* settings that VS Code's built-in TypeScript extension prefers.

The reported case is formatting (vuejs/language-tools#6150), but the same gap affects every setting the package reads. @HiDeoo found it while debugging js/ts.preferences.autoImportSpecifierExcludeRegexes in an Astro project, where the typescript.preferences.* spelling worked and the unified one did not.

This supersedes #126, which only covered js/ts.format.*.

Change

Add createUnifiedConfig(), which resolves the unified js/ts section and the language-specific section once, then reads each setting with the unified value taking precedence:

const config = await createUnifiedConfig(ctx, document);
config.get('format.semicolons', 'ignore');

Every language-specific read in the package now goes through it:

area settings
formatting js/ts.format.* (18 keys, incl. indentSwitchCase)
preferences js/ts.preferences.*
suggestions js/ts.suggest.*
validation js/ts.validate.enabled
auto-closing tags js/ts.autoClosingTags.enabled
inlay hints js/ts.inlayHints.*

getUserPreferences() is included, which resolves the review comment. renameShorthandProperties and unstable have no unified counterpart and keep reading the language-specific section.

Why the default table

VS Code's built-in extension decides with WorkspaceConfiguration.inspect() — it knows whether a unified setting was actually modified. A language server cannot: workspace/configuration returns the resolved value with the registered default already merged in, so an unset setting is indistinguishable from one set to its default.

The helper therefore treats a unified value equal to its registered default as unset, and keeps every default in one table (unifiedDefaults) that mirrors contributes.configuration of VS Code's typescript-language-features package.json. This is the best available approach: the hardcoded-default comparison is only a heuristic, and a table is the smallest version of that heuristic — one line per changed default instead of 40 scattered ?? default sites.

I verified the table mechanically against VS Code's current package.json: all 50 keys match, and every relevant js/ts.* setting that the service reads is covered.

Remaining limitation, unchanged in kind from #126: explicitly setting a unified key to its exact default while also setting the deprecated counterpart to a different value resolves to the deprecated value. VS Code would take the unified default. This needs inspect() over the wire to fix.

Options considered:

  • A custom LSP request carrying inspect(). Only correct fix. Needs a LanguageServiceEnvironment API plus a client-side handler in @volar/vscode / vuejs/language-tools, so it is out of scope here. If it lands, createUnifiedConfig is the single place to switch over.
  • Prefer unified whenever the key is present. Incorrect — VS Code resolves the whole section, so this would ignore every typescript.format.* setting for users who only configured those.
  • Read only the language-specific section when the unified one equals the default. That is what this PR does.

Separately, workspace/configuration is requested without a document scope, so language-scoped overrides such as "[typescript]": { "js/ts.format.semicolons": "insert" } are still not visible to the server. That is a pre-existing limitation of the host request, not of this change.

One pre-existing read is deliberately left alone: the auto-import cache's setPreferences still reads typescript.preferences. It applies to the whole language service rather than a document, so there is no language section to fall back to; moving it to js/ts.preferences would need a separate decision about which section it should use. The includeInlayParameterNameHints mapping also keeps its current behavior, where 'none' maps to undefined.

Test plan

  • pnpm run build, pnpm run test, pnpm run lint, dprint check
  • New specs: getUnifiedConfig, getFormatCodeSettings, getUserPreferences, covering unified precedence, fallback when the unified value equals its default, language-specific fallback, a host that returns only explicitly-set values (@volar/kit), defaults, and pattern normalization.

Closes vuejs/language-tools#6150
Supersedes #126

The TypeScript service only read the deprecated per-language
`typescript.*` / `javascript.*` settings, so a Vue `<script>` block
ignored the unified `js/ts.*` settings that VS Code's built-in TS
extension prefers. This affected formatting, user preferences
(`autoImportSpecifierExcludeRegexes` among them), suggestions,
validation and auto-closing tags.

Add `createUnifiedConfig()`, which resolves both the unified `js/ts`
section and the language-specific section once and reads a setting with
the unified value taking precedence. Wire every language-specific read in
the package through it, so the fallback applies to all JS/TS settings
rather than formats only.

VS Code can tell "unset" from "explicitly set to the default" because it
has `WorkspaceConfiguration.inspect()`. A language server cannot:
`workspace/configuration` returns the resolved value with the registered
default included. The helper therefore treats a unified value equal to
its registered default as unset and keeps the defaults in one table,
kept in sync with VS Code's `typescript-language-features` package.json.

Closes vuejs/language-tools#6150
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.

Unified js/ts.format settings are ignored in Vue script blocks

1 participant