Skip to content

fix(typescript): respect resolveHiddenExtensions when a host pre-installs resolveModuleNameLiterals#309

Open
NullVoxPopuli wants to merge 1 commit into
volarjs:masterfrom
NullVoxPopuli:fix/resolve-hidden-extensions-watch-mode
Open

fix(typescript): respect resolveHiddenExtensions when a host pre-installs resolveModuleNameLiterals#309
NullVoxPopuli wants to merge 1 commit into
volarjs:masterfrom
NullVoxPopuli:fix/resolve-hidden-extensions-watch-mode

Conversation

@NullVoxPopuli

Copy link
Copy Markdown

Summary

When any registered language plugin sets typescript.resolveHiddenExtensions: true, the wrapped moduleResolutionHost.fileExists in resolveModuleName.ts is what makes extensionless imports of plugin-extension files resolvable (it answers Foo.d.ts as true when Foo.<ext> exists on disk, so tsc's hard-coded tryAddingExtensions accepts the lookup).

The fast-path in proxyCreateProgram short-circuits a resolution batch back to the original host whenever no import literal ends in a plugin extension. In one-shot tsc the original host has no resolveModuleNameLiterals installed, so the condition is false and volar's wrapper runs.

But tsc --watch installs a cached resolver on the compiler host before volar's proxy runs:

// typescript/lib/_tsc.js (createWatchProgram)
compilerHost.resolveModuleNameLiterals = resolutionCache.resolveModuleNameLiterals.bind(resolutionCache);

With that resolver defined, extensionless imports now take the fast-path and bypass the wrapper. The host's plain fileExists doesn't know about the .<ext>.d.ts shim, so the import fails with TS2307 in watch mode while succeeding in one-shot mode.

This was hit by glint for .gts files: import Foo from './Foo' (where Foo.gts exists) works under ember-tsc but errors under ember-tsc --watch. Same shape as the TS Plugin mode limitation already tracked at typed-ember/glint#806.

Change

Skip the fast-path when any plugin opts in to resolveHiddenExtensions, matching the existing pluginExtensions check. Applied to both resolveModuleNameLiterals and resolveModuleNames.

Test plan

  • Added packages/typescript/tests/proxyCreateProgram.spec.ts with two cases:
    • With resolveHiddenExtensions: true, extensionless imports must not be delegated to the host's resolveModuleNameLiterals.
    • Without resolveHiddenExtensions, the existing fast-path is preserved — extensionless imports still delegate to the host.
  • pnpm build clean
  • pnpm test — 72 passed (10 files), including the two new cases. Reverting the production change makes the first case fail, confirming it catches the regression.
  • Verified end-to-end against the original repro in glint: with this patch applied to the @volar/typescript source and rebuilt, ember-tsc --watch against a project with extensionless .gts imports reports Found 0 errors (was TS2307: Cannot find module './Foo').

…hen a plugin sets resolveHiddenExtensions

When any registered language plugin uses `resolveHiddenExtensions: true`,
the wrapped `moduleResolutionHost.fileExists` in resolveModuleName.ts is
what makes extensionless imports of plugin-extension files resolvable
(it answers `Foo.d.ts` as true when `Foo.<ext>` exists on disk).

The fast-path in proxyCreateProgram delegates a module-resolution batch
back to the original host whenever none of the import literals end in a
plugin extension. In one-shot `tsc` the original host has no resolver
installed, so the fast-path is a no-op and volar's wrapper runs.

But `tsc --watch` installs `resolutionCache.resolveModuleNameLiterals`
on the compiler host before volar's proxy runs. With that resolver
defined, extensionless imports take the fast-path and bypass the
wrapper, so e.g. glint's `import Foo from './Foo'` (where `Foo.gts`
exists) fails with TS2307 in watch mode while succeeding in one-shot
mode.

Skip the fast-path when any plugin opts in to `resolveHiddenExtensions`,
matching the existing pluginExtensions check.

Reported by https://github.com/typed-ember/glint
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