From 2d367f93def04aedeb277d2757a2cca879b0d3af Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 18 Dec 2025 00:01:30 +0000 Subject: [PATCH 1/5] Initial plan From 0a6a52e20b4f5c5b57c4da8b2b58e13d3aba09c4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 18 Dec 2025 00:14:32 +0000 Subject: [PATCH 2/5] Add test case for declaration emit with noResolve and cast type Co-authored-by: jakebailey <5341706+jakebailey@users.noreply.github.com> --- .../compiler/declarationEmitRetainsImportOfCastType.ts | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 testdata/tests/cases/compiler/declarationEmitRetainsImportOfCastType.ts diff --git a/testdata/tests/cases/compiler/declarationEmitRetainsImportOfCastType.ts b/testdata/tests/cases/compiler/declarationEmitRetainsImportOfCastType.ts new file mode 100644 index 0000000000..6ff7146254 --- /dev/null +++ b/testdata/tests/cases/compiler/declarationEmitRetainsImportOfCastType.ts @@ -0,0 +1,8 @@ +// @declaration: true +// @noResolve: true + +import { WritableAtom } from 'jotai' + +export function focusAtom() { + return null as unknown as WritableAtom +} From 4b18c14f615b3c53f8b112c815fcd6ad27688719 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 18 Dec 2025 00:53:44 +0000 Subject: [PATCH 3/5] Fix declaration emit to retain imports for unresolved type aliases When a type has an alias pointing to an unresolved symbol (e.g., from an unresolved import with --noResolve), the import was being dropped from declaration emit. This fix: 1. In ToTypeReferenceNode: When the alias symbol is unresolved, look up the local import binding by name and track it to mark it visible. 2. In lookupSymbolChain: Skip tracking unresolved symbols since they have no declarations and cause false accessibility errors. The local import binding is tracked separately. Fixes #2328 Co-authored-by: jakebailey <5341706+jakebailey@users.noreply.github.com> --- internal/checker/nodebuilderimpl.go | 18 +++++++++++++-- ...tionEmitRetainsImportOfCastType.errors.txt | 12 ++++++++++ .../declarationEmitRetainsImportOfCastType.js | 22 +++++++++++++++++++ ...arationEmitRetainsImportOfCastType.symbols | 13 +++++++++++ ...clarationEmitRetainsImportOfCastType.types | 14 ++++++++++++ 5 files changed, 77 insertions(+), 2 deletions(-) create mode 100644 testdata/baselines/reference/compiler/declarationEmitRetainsImportOfCastType.errors.txt create mode 100644 testdata/baselines/reference/compiler/declarationEmitRetainsImportOfCastType.js create mode 100644 testdata/baselines/reference/compiler/declarationEmitRetainsImportOfCastType.symbols create mode 100644 testdata/baselines/reference/compiler/declarationEmitRetainsImportOfCastType.types diff --git a/internal/checker/nodebuilderimpl.go b/internal/checker/nodebuilderimpl.go index 1fb4c35794..d39bea699e 100644 --- a/internal/checker/nodebuilderimpl.go +++ b/internal/checker/nodebuilderimpl.go @@ -944,7 +944,11 @@ func (b *NodeBuilderImpl) lookupTypeParameterNodes(chain []*ast.Symbol, index in // TODO: move `lookupSymbolChain` and co to `symbolaccessibility.go` (but getSpecifierForModuleSymbol uses much context which makes that hard?) func (b *NodeBuilderImpl) lookupSymbolChain(symbol *ast.Symbol, meaning ast.SymbolFlags, yieldModuleSymbol bool) []*ast.Symbol { - b.ctx.tracker.TrackSymbol(symbol, b.ctx.enclosingDeclaration, meaning) + // Skip tracking for unresolved symbols - they have no declarations and cause false accessibility errors. + // The local import binding (if any) should be tracked separately. + if symbol.CheckFlags&ast.CheckFlagsUnresolved == 0 { + b.ctx.tracker.TrackSymbol(symbol, b.ctx.enclosingDeclaration, meaning) + } return b.lookupSymbolChainWorker(symbol, meaning, yieldModuleSymbol) } @@ -3111,5 +3115,15 @@ func (b *NodeBuilderImpl) newStringLiteralEx(text string, isSingleQuote bool) *a // Direct serialization core functions for types, type aliases, and symbols func (t *TypeAlias) ToTypeReferenceNode(b *NodeBuilderImpl) *ast.Node { - return b.f.NewTypeReferenceNode(b.symbolToEntityNameNode(t.Symbol()), b.mapToTypeNodes(t.TypeArguments(), false /*isBareList*/)) + sym := t.Symbol() + // For unresolved symbols (e.g., from unresolved imports), try to find and track + // the local import binding by resolving the name without following the alias + if sym.CheckFlags&ast.CheckFlagsUnresolved != 0 && b.ctx.enclosingDeclaration != nil { + localSym := b.ch.resolveName(b.ctx.enclosingDeclaration, sym.Name, ast.SymbolFlagsType|ast.SymbolFlagsAlias, nil, false, false) + if localSym != nil && localSym.Flags&ast.SymbolFlagsAlias != 0 { + // Track the local alias symbol (the import specifier) to mark it visible + b.ctx.tracker.TrackSymbol(localSym, b.ctx.enclosingDeclaration, ast.SymbolFlagsType) + } + } + return b.f.NewTypeReferenceNode(b.symbolToName(sym, ast.SymbolFlagsType, false), b.mapToTypeNodes(t.TypeArguments(), false /*isBareList*/)) } diff --git a/testdata/baselines/reference/compiler/declarationEmitRetainsImportOfCastType.errors.txt b/testdata/baselines/reference/compiler/declarationEmitRetainsImportOfCastType.errors.txt new file mode 100644 index 0000000000..fb251c227e --- /dev/null +++ b/testdata/baselines/reference/compiler/declarationEmitRetainsImportOfCastType.errors.txt @@ -0,0 +1,12 @@ +declarationEmitRetainsImportOfCastType.ts(1,30): error TS2307: Cannot find module 'jotai' or its corresponding type declarations. + + +==== declarationEmitRetainsImportOfCastType.ts (1 errors) ==== + import { WritableAtom } from 'jotai' + ~~~~~~~ +!!! error TS2307: Cannot find module 'jotai' or its corresponding type declarations. + + export function focusAtom() { + return null as unknown as WritableAtom + } + \ No newline at end of file diff --git a/testdata/baselines/reference/compiler/declarationEmitRetainsImportOfCastType.js b/testdata/baselines/reference/compiler/declarationEmitRetainsImportOfCastType.js new file mode 100644 index 0000000000..f9dfd1dc4f --- /dev/null +++ b/testdata/baselines/reference/compiler/declarationEmitRetainsImportOfCastType.js @@ -0,0 +1,22 @@ +//// [tests/cases/compiler/declarationEmitRetainsImportOfCastType.ts] //// + +//// [declarationEmitRetainsImportOfCastType.ts] +import { WritableAtom } from 'jotai' + +export function focusAtom() { + return null as unknown as WritableAtom +} + + +//// [declarationEmitRetainsImportOfCastType.js] +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.focusAtom = focusAtom; +function focusAtom() { + return null; +} + + +//// [declarationEmitRetainsImportOfCastType.d.ts] +import { WritableAtom } from 'jotai'; +export declare function focusAtom(): WritableAtom; diff --git a/testdata/baselines/reference/compiler/declarationEmitRetainsImportOfCastType.symbols b/testdata/baselines/reference/compiler/declarationEmitRetainsImportOfCastType.symbols new file mode 100644 index 0000000000..cc29ac7553 --- /dev/null +++ b/testdata/baselines/reference/compiler/declarationEmitRetainsImportOfCastType.symbols @@ -0,0 +1,13 @@ +//// [tests/cases/compiler/declarationEmitRetainsImportOfCastType.ts] //// + +=== declarationEmitRetainsImportOfCastType.ts === +import { WritableAtom } from 'jotai' +>WritableAtom : Symbol(WritableAtom, Decl(declarationEmitRetainsImportOfCastType.ts, 0, 8)) + +export function focusAtom() { +>focusAtom : Symbol(focusAtom, Decl(declarationEmitRetainsImportOfCastType.ts, 0, 36)) + + return null as unknown as WritableAtom +>WritableAtom : Symbol(WritableAtom, Decl(declarationEmitRetainsImportOfCastType.ts, 0, 8)) +} + diff --git a/testdata/baselines/reference/compiler/declarationEmitRetainsImportOfCastType.types b/testdata/baselines/reference/compiler/declarationEmitRetainsImportOfCastType.types new file mode 100644 index 0000000000..dfd4717f20 --- /dev/null +++ b/testdata/baselines/reference/compiler/declarationEmitRetainsImportOfCastType.types @@ -0,0 +1,14 @@ +//// [tests/cases/compiler/declarationEmitRetainsImportOfCastType.ts] //// + +=== declarationEmitRetainsImportOfCastType.ts === +import { WritableAtom } from 'jotai' +>WritableAtom : any + +export function focusAtom() { +>focusAtom : () => WritableAtom + + return null as unknown as WritableAtom +>null as unknown as WritableAtom : WritableAtom +>null as unknown : unknown +} + From be8295c8cae09132beebb8ed9dfd710e49878b11 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 18 Dec 2025 06:52:12 +0000 Subject: [PATCH 4/5] Refine fix for declaration emit with unresolved type aliases Use symbolToEntityNameNode for unresolved symbols to avoid false accessibility errors, while still tracking the local import binding to ensure imports are retained in declaration output. The previous approach of skipping TrackSymbol for all unresolved symbols in lookupSymbolChain was too broad and affected qualified name printing. Co-authored-by: jakebailey <5341706+jakebailey@users.noreply.github.com> --- internal/checker/nodebuilderimpl.go | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/internal/checker/nodebuilderimpl.go b/internal/checker/nodebuilderimpl.go index d39bea699e..b08dffdaf2 100644 --- a/internal/checker/nodebuilderimpl.go +++ b/internal/checker/nodebuilderimpl.go @@ -944,11 +944,7 @@ func (b *NodeBuilderImpl) lookupTypeParameterNodes(chain []*ast.Symbol, index in // TODO: move `lookupSymbolChain` and co to `symbolaccessibility.go` (but getSpecifierForModuleSymbol uses much context which makes that hard?) func (b *NodeBuilderImpl) lookupSymbolChain(symbol *ast.Symbol, meaning ast.SymbolFlags, yieldModuleSymbol bool) []*ast.Symbol { - // Skip tracking for unresolved symbols - they have no declarations and cause false accessibility errors. - // The local import binding (if any) should be tracked separately. - if symbol.CheckFlags&ast.CheckFlagsUnresolved == 0 { - b.ctx.tracker.TrackSymbol(symbol, b.ctx.enclosingDeclaration, meaning) - } + b.ctx.tracker.TrackSymbol(symbol, b.ctx.enclosingDeclaration, meaning) return b.lookupSymbolChainWorker(symbol, meaning, yieldModuleSymbol) } @@ -3124,6 +3120,9 @@ func (t *TypeAlias) ToTypeReferenceNode(b *NodeBuilderImpl) *ast.Node { // Track the local alias symbol (the import specifier) to mark it visible b.ctx.tracker.TrackSymbol(localSym, b.ctx.enclosingDeclaration, ast.SymbolFlagsType) } + // For unresolved symbols, use symbolToEntityNameNode to avoid false accessibility errors + // since we've already tracked the local import binding above + return b.f.NewTypeReferenceNode(b.symbolToEntityNameNode(sym), b.mapToTypeNodes(t.TypeArguments(), false /*isBareList*/)) } return b.f.NewTypeReferenceNode(b.symbolToName(sym, ast.SymbolFlagsType, false), b.mapToTypeNodes(t.TypeArguments(), false /*isBareList*/)) } From e9f8f3f5a0833fade3bcff55b32033f618730ab0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 18 Dec 2025 07:24:55 +0000 Subject: [PATCH 5/5] Fix declaration emit to retain imports for unresolved type aliases Track local import bindings for unresolved type aliases so imports are retained in declaration emit when using --noResolve with type assertions. This also improves isolatedDeclarationErrorTypes1 baseline by correctly retaining the import for unresolved types, which prevents dangling type references in the generated .d.ts files. Co-authored-by: jakebailey <5341706+jakebailey@users.noreply.github.com> --- internal/checker/nodebuilderimpl.go | 10 ++++------ .../compiler/isolatedDeclarationErrorTypes1.js | 1 + .../compiler/isolatedDeclarationErrorTypes1.js.diff | 5 ++--- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/internal/checker/nodebuilderimpl.go b/internal/checker/nodebuilderimpl.go index b08dffdaf2..65ead175d0 100644 --- a/internal/checker/nodebuilderimpl.go +++ b/internal/checker/nodebuilderimpl.go @@ -3112,17 +3112,15 @@ func (b *NodeBuilderImpl) newStringLiteralEx(text string, isSingleQuote bool) *a func (t *TypeAlias) ToTypeReferenceNode(b *NodeBuilderImpl) *ast.Node { sym := t.Symbol() - // For unresolved symbols (e.g., from unresolved imports), try to find and track - // the local import binding by resolving the name without following the alias + // For unresolved symbols (e.g., from unresolved imports with --noResolve), try to find and track + // the local import binding by resolving the name without following the alias. + // This ensures the import is retained in declaration emit. if sym.CheckFlags&ast.CheckFlagsUnresolved != 0 && b.ctx.enclosingDeclaration != nil { localSym := b.ch.resolveName(b.ctx.enclosingDeclaration, sym.Name, ast.SymbolFlagsType|ast.SymbolFlagsAlias, nil, false, false) if localSym != nil && localSym.Flags&ast.SymbolFlagsAlias != 0 { // Track the local alias symbol (the import specifier) to mark it visible b.ctx.tracker.TrackSymbol(localSym, b.ctx.enclosingDeclaration, ast.SymbolFlagsType) } - // For unresolved symbols, use symbolToEntityNameNode to avoid false accessibility errors - // since we've already tracked the local import binding above - return b.f.NewTypeReferenceNode(b.symbolToEntityNameNode(sym), b.mapToTypeNodes(t.TypeArguments(), false /*isBareList*/)) } - return b.f.NewTypeReferenceNode(b.symbolToName(sym, ast.SymbolFlagsType, false), b.mapToTypeNodes(t.TypeArguments(), false /*isBareList*/)) + return b.f.NewTypeReferenceNode(b.symbolToEntityNameNode(sym), b.mapToTypeNodes(t.TypeArguments(), false /*isBareList*/)) } diff --git a/testdata/baselines/reference/submodule/compiler/isolatedDeclarationErrorTypes1.js b/testdata/baselines/reference/submodule/compiler/isolatedDeclarationErrorTypes1.js index 64ad2de25b..a2c9a669a4 100644 --- a/testdata/baselines/reference/submodule/compiler/isolatedDeclarationErrorTypes1.js +++ b/testdata/baselines/reference/submodule/compiler/isolatedDeclarationErrorTypes1.js @@ -24,6 +24,7 @@ exports.foo3 = foo3; //// [isolatedDeclarationErrorTypes1.d.ts] +import { Unresolved } from "foo"; export declare const foo1: (type?: any) => void; export declare const foo2: (type?: any) => void; export declare const foo3: (type: Unresolved) => void; diff --git a/testdata/baselines/reference/submodule/compiler/isolatedDeclarationErrorTypes1.js.diff b/testdata/baselines/reference/submodule/compiler/isolatedDeclarationErrorTypes1.js.diff index 4134efb25c..0d416e8019 100644 --- a/testdata/baselines/reference/submodule/compiler/isolatedDeclarationErrorTypes1.js.diff +++ b/testdata/baselines/reference/submodule/compiler/isolatedDeclarationErrorTypes1.js.diff @@ -1,10 +1,9 @@ --- old.isolatedDeclarationErrorTypes1.js +++ new.isolatedDeclarationErrorTypes1.js -@@= skipped -23, +23 lines =@@ - +@@= skipped -24, +24 lines =@@ //// [isolatedDeclarationErrorTypes1.d.ts] --import { Unresolved } from "foo"; + import { Unresolved } from "foo"; -export declare const foo1: (type?: Unresolved) => void; -export declare const foo2: (type?: Unresolved | undefined) => void; +export declare const foo1: (type?: any) => void;