Skip to content

Fix declaration emit synthesizing extensionless import() specifier under allowImportingTsExtensions + nodenext#4649

Open
RyanCavanaugh with Copilot wants to merge 2 commits into
mainfrom
copilot/fix-import-extension-issue
Open

Fix declaration emit synthesizing extensionless import() specifier under allowImportingTsExtensions + nodenext#4649
RyanCavanaugh with Copilot wants to merge 2 commits into
mainfrom
copilot/fix-import-extension-issue

Conversation

Copilot AI commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

When using allowImportingTsExtensions: true with .ts-extension relative imports under module: nodenext, declaration emit was generating invalid extensionless import() type references for node_modules packages — import("mylib/lib/Box") instead of import("mylib/lib/Box.js") — causing TS2307 errors in the emitted .d.ts.

Root cause

GetAllowedEndingsInPreferredOrder in preferences.go checked the ESM+NodeNext special case as:

if syntaxImpliedNodeFormat == core.ResolutionModeESM && moduleResolutionIsNodeNext {

TypeScript 6 uses (syntaxImpliedNodeFormat ?? impliedNodeFormat), falling back to the file's implied node format when syntaxImpliedNodeFormat is not provided. The Go port was missing this fallback.

When tryGetModuleNameAsNodeModule calls with ResolutionModeNone, the ESM check was always false, yielding [TsExtension, Minimal, JsExtension, Index] instead of the correct [TsExtension, JsExtension]. With Minimal before JsExtension, processEnding picked the extensionless form for .d.ts declaration files.

Fix

  • internal/modulespecifiers/preferences.go: When syntaxImpliedNodeFormat is ResolutionModeNone, fall back to resolutionMode (the file's default) before the ESM+NodeNext check — matching TypeScript's ?? semantics.

  • testdata/tests/cases/compiler/declarationEmitImportSpecifierWithTsExtensionRelativeImport.ts: Regression test covering the exact repro: ESM package, .ts-extension relative import chain, node_modules subpath with .js extension, verified mod.d.ts output is now import("mylib/lib/Box.js").Box<string> with no TS2307.

…nsions

When a file uses `.ts`-extension relative imports (allowImportingTsExtensions),
declaration emit was synthesizing extensionless import() specifiers for
non-relative (node_modules) module references, which is invalid under nodenext.

Root cause: GetAllowedEndingsInPreferredOrder used syntaxImpliedNodeFormat
directly without falling back to the file's implied format when it's None.
TypeScript 6 uses (syntaxImpliedNodeFormat ?? impliedNodeFormat), properly
falling back to the ESM file's implied mode. This caused the wrong allowedEndings
ordering ([TsExtension, Minimal, JsExtension, ...] instead of [TsExtension, JsExtension]),
which led processEnding to return extensionless paths for .d.ts files.

The fix adds the same fallback: when syntaxImpliedNodeFormat is ResolutionModeNone,
use resolutionMode (the file's default) for the ESM+NodeNext check.

Fixes #4616

Co-authored-by: RyanCavanaugh <6685088+RyanCavanaugh@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix declaration emit for extensionless import() specifier Fix declaration emit synthesizing extensionless import() specifier under allowImportingTsExtensions + nodenext Jul 15, 2026
Copilot AI requested a review from RyanCavanaugh July 15, 2026 20:50
@andrewbranch
andrewbranch marked this pull request as ready for review July 15, 2026 23:46
Copilot AI review requested due to automatic review settings July 15, 2026 23:46
@andrewbranch
andrewbranch enabled auto-merge July 15, 2026 23:47

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes a declaration emit module-specifier selection bug in the tsgo port that could synthesize extensionless import() type references under --module nodenext when allowImportingTsExtensions is enabled, leading to invalid emitted .d.ts (e.g. TS2307 due to missing .js).

Changes:

  • Fix GetAllowedEndingsInPreferredOrder to fall back from syntaxImpliedNodeFormat to the file’s implied/default resolution mode when the syntax-implied mode is not provided (ResolutionModeNone), matching upstream ?? semantics.
  • Add a compiler regression test reproducing the .ts-extension relative import chain and asserting .js is preserved in synthesized import() specifiers for node_modules subpaths.
  • Add the corresponding reference baselines for the new test.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated no comments.

Show a summary per file
File Description
internal/modulespecifiers/preferences.go Implements the ResolutionModeNone fallback so ESM+NodeNext ending preferences are computed correctly.
testdata/tests/cases/compiler/declarationEmitImportSpecifierWithTsExtensionRelativeImport.ts New regression test covering the reported nodenext + allowImportingTsExtensions declaration emit scenario.
testdata/baselines/reference/compiler/declarationEmitImportSpecifierWithTsExtensionRelativeImport.js Reference baseline verifying emitted mod.d.ts contains import("mylib/lib/Box.js")....
testdata/baselines/reference/compiler/declarationEmitImportSpecifierWithTsExtensionRelativeImport.types Reference types baseline for the new test case.
testdata/baselines/reference/compiler/declarationEmitImportSpecifierWithTsExtensionRelativeImport.symbols Reference symbols baseline for the new test case.

@RyanCavanaugh
RyanCavanaugh marked this pull request as draft July 16, 2026 20:01
auto-merge was automatically disabled July 16, 2026 20:01

Pull request was converted to draft

@RyanCavanaugh
RyanCavanaugh marked this pull request as ready for review July 16, 2026 20:01
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.

Behavior difference: declaration emit synthesizes extensionless import() specifier under allowImportingTsExtensions, invalid in nodenext

4 participants