From a92014d0b8b46c9da02367b2604b50d95a1f7032 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 2 Feb 2026 23:44:50 +0000 Subject: [PATCH 01/11] Initial plan From af210809e097ffb1f63a0427eb1c6fb2627dcdaa Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 2 Feb 2026 23:53:20 +0000 Subject: [PATCH 02/11] Add test case for import value only used as type bug Co-authored-by: jakebailey <5341706+jakebailey@users.noreply.github.com> --- .../compiler/importValueOnlyUsedAsType.js | 58 ++++++++++++++++ .../importValueOnlyUsedAsType.symbols | 69 +++++++++++++++++++ .../compiler/importValueOnlyUsedAsType.types | 61 ++++++++++++++++ .../compiler/importValueOnlyUsedAsType.ts | 32 +++++++++ 4 files changed, 220 insertions(+) create mode 100644 testdata/baselines/reference/compiler/importValueOnlyUsedAsType.js create mode 100644 testdata/baselines/reference/compiler/importValueOnlyUsedAsType.symbols create mode 100644 testdata/baselines/reference/compiler/importValueOnlyUsedAsType.types create mode 100644 testdata/tests/cases/compiler/importValueOnlyUsedAsType.ts diff --git a/testdata/baselines/reference/compiler/importValueOnlyUsedAsType.js b/testdata/baselines/reference/compiler/importValueOnlyUsedAsType.js new file mode 100644 index 00000000000..5e69683855a --- /dev/null +++ b/testdata/baselines/reference/compiler/importValueOnlyUsedAsType.js @@ -0,0 +1,58 @@ +//// [tests/cases/compiler/importValueOnlyUsedAsType.ts] //// + +//// [provider.ts] +export class Value { + data: string = ""; +} +export type ValueData = { data: string }; + +//// [consumer.ts] +// Import Value as a value (no `type` keyword), but only use it in type positions +import { Value, type ValueData } from "./provider"; + +// Value is ONLY used in type positions: +export interface Record { + getValue(): Value; // return type + setValue(value: Value): void; // parameter type + readonly currentValue: Value; // type annotation +} + +export function processRecord( + value: Value, // parameter type + callback: (result: Value) => void, // parameter type in callback +): Value { + // return type + callback(value); + return value; +} + +export class BaseProcessor { + current: Value | null = null; +} + + +//// [provider.js] +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.Value = void 0; +class Value { + data = ""; +} +exports.Value = Value; +//// [consumer.js] +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.BaseProcessor = void 0; +exports.processRecord = processRecord; +// Import Value as a value (no `type` keyword), but only use it in type positions +require("./provider"); +function processRecord(value, // parameter type +callback) { + // return type + callback(value); + return value; +} +class BaseProcessor { + current = null; +} +exports.BaseProcessor = BaseProcessor; diff --git a/testdata/baselines/reference/compiler/importValueOnlyUsedAsType.symbols b/testdata/baselines/reference/compiler/importValueOnlyUsedAsType.symbols new file mode 100644 index 00000000000..8e817f9971d --- /dev/null +++ b/testdata/baselines/reference/compiler/importValueOnlyUsedAsType.symbols @@ -0,0 +1,69 @@ +//// [tests/cases/compiler/importValueOnlyUsedAsType.ts] //// + +=== provider.ts === +export class Value { +>Value : Symbol(Value, Decl(provider.ts, 0, 0)) + + data: string = ""; +>data : Symbol(Value.data, Decl(provider.ts, 0, 20)) +} +export type ValueData = { data: string }; +>ValueData : Symbol(ValueData, Decl(provider.ts, 2, 1)) +>data : Symbol(data, Decl(provider.ts, 3, 25)) + +=== consumer.ts === +// Import Value as a value (no `type` keyword), but only use it in type positions +import { Value, type ValueData } from "./provider"; +>Value : Symbol(Value, Decl(consumer.ts, 1, 8)) +>ValueData : Symbol(ValueData, Decl(consumer.ts, 1, 15)) + +// Value is ONLY used in type positions: +export interface Record { +>Record : Symbol(Record, Decl(consumer.ts, 1, 51)) + + getValue(): Value; // return type +>getValue : Symbol(Record.getValue, Decl(consumer.ts, 4, 25)) +>Value : Symbol(Value, Decl(consumer.ts, 1, 8)) + + setValue(value: Value): void; // parameter type +>setValue : Symbol(Record.setValue, Decl(consumer.ts, 5, 22)) +>value : Symbol(value, Decl(consumer.ts, 6, 13)) +>Value : Symbol(Value, Decl(consumer.ts, 1, 8)) + + readonly currentValue: Value; // type annotation +>currentValue : Symbol(Record.currentValue, Decl(consumer.ts, 6, 33)) +>Value : Symbol(Value, Decl(consumer.ts, 1, 8)) +} + +export function processRecord( +>processRecord : Symbol(processRecord, Decl(consumer.ts, 8, 1)) + + value: Value, // parameter type +>value : Symbol(value, Decl(consumer.ts, 10, 30)) +>Value : Symbol(Value, Decl(consumer.ts, 1, 8)) + + callback: (result: Value) => void, // parameter type in callback +>callback : Symbol(callback, Decl(consumer.ts, 11, 17)) +>result : Symbol(result, Decl(consumer.ts, 12, 15)) +>Value : Symbol(Value, Decl(consumer.ts, 1, 8)) + +): Value { +>Value : Symbol(Value, Decl(consumer.ts, 1, 8)) + + // return type + callback(value); +>callback : Symbol(callback, Decl(consumer.ts, 11, 17)) +>value : Symbol(value, Decl(consumer.ts, 10, 30)) + + return value; +>value : Symbol(value, Decl(consumer.ts, 10, 30)) +} + +export class BaseProcessor { +>BaseProcessor : Symbol(BaseProcessor, Decl(consumer.ts, 17, 1)) + + current: Value | null = null; +>current : Symbol(BaseProcessor.current, Decl(consumer.ts, 19, 28)) +>Value : Symbol(Value, Decl(consumer.ts, 1, 8)) +} + diff --git a/testdata/baselines/reference/compiler/importValueOnlyUsedAsType.types b/testdata/baselines/reference/compiler/importValueOnlyUsedAsType.types new file mode 100644 index 00000000000..fa94ce92e09 --- /dev/null +++ b/testdata/baselines/reference/compiler/importValueOnlyUsedAsType.types @@ -0,0 +1,61 @@ +//// [tests/cases/compiler/importValueOnlyUsedAsType.ts] //// + +=== provider.ts === +export class Value { +>Value : Value + + data: string = ""; +>data : string +>"" : "" +} +export type ValueData = { data: string }; +>ValueData : ValueData +>data : string + +=== consumer.ts === +// Import Value as a value (no `type` keyword), but only use it in type positions +import { Value, type ValueData } from "./provider"; +>Value : typeof Value +>ValueData : any + +// Value is ONLY used in type positions: +export interface Record { + getValue(): Value; // return type +>getValue : () => Value + + setValue(value: Value): void; // parameter type +>setValue : (value: Value) => void +>value : Value + + readonly currentValue: Value; // type annotation +>currentValue : Value +} + +export function processRecord( +>processRecord : (value: Value, callback: (result: Value) => void) => Value + + value: Value, // parameter type +>value : Value + + callback: (result: Value) => void, // parameter type in callback +>callback : (result: Value) => void +>result : Value + +): Value { + // return type + callback(value); +>callback(value) : void +>callback : (result: Value) => void +>value : Value + + return value; +>value : Value +} + +export class BaseProcessor { +>BaseProcessor : BaseProcessor + + current: Value | null = null; +>current : Value +} + diff --git a/testdata/tests/cases/compiler/importValueOnlyUsedAsType.ts b/testdata/tests/cases/compiler/importValueOnlyUsedAsType.ts new file mode 100644 index 00000000000..731ea852e31 --- /dev/null +++ b/testdata/tests/cases/compiler/importValueOnlyUsedAsType.ts @@ -0,0 +1,32 @@ +// @module: commonjs +// @target: es2020 + +// @filename: provider.ts +export class Value { + data: string = ""; +} +export type ValueData = { data: string }; + +// @filename: consumer.ts +// Import Value as a value (no `type` keyword), but only use it in type positions +import { Value, type ValueData } from "./provider"; + +// Value is ONLY used in type positions: +export interface Record { + getValue(): Value; // return type + setValue(value: Value): void; // parameter type + readonly currentValue: Value; // type annotation +} + +export function processRecord( + value: Value, // parameter type + callback: (result: Value) => void, // parameter type in callback +): Value { + // return type + callback(value); + return value; +} + +export class BaseProcessor { + current: Value | null = null; +} From d45ec2bed4803313adc4698877d17f1828d28321 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Feb 2026 00:08:36 +0000 Subject: [PATCH 03/11] Add investigation notes for import elision bug Found that tsgo incorrectly emits require() when: - Import contains both 'type' and non-type specifiers - Non-type specifier is only used in type positions Minimal repro: import { Value, type ValueData } from './provider'; where Value is only used as a type annotation. --- INVESTIGATION.md | 171 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 171 insertions(+) create mode 100644 INVESTIGATION.md diff --git a/INVESTIGATION.md b/INVESTIGATION.md new file mode 100644 index 00000000000..50ec1abd2d9 --- /dev/null +++ b/INVESTIGATION.md @@ -0,0 +1,171 @@ +# Investigation: Incorrect `require` Emission for Type-Only Imports + +## Problem Summary +`tsgo` incorrectly emits a `require` statement when an ES6 import contains: +1. A `type` import specifier (e.g., `type ValueData`) +2. A regular import specifier (e.g., `Value`) that is ONLY used in type positions + +## Reproduction + +### Minimal Test Case +```typescript +// provider.ts +export class Value { + data: string = ""; +} +export type ValueData = { data: string }; + +// consumer.ts - BROKEN +import { Value, type ValueData } from "./provider"; +export function test(value: Value): Value { + return null as any; +} + +// consumer-alt.ts - WORKS +import { Value } from "./provider"; +export function test(value: Value): Value { + return null as any; +} +``` + +### Actual Output (tsgo) +```javascript +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.test = test; +require("./provider"); // ❌ THIS SHOULD NOT BE HERE +function test(value) { + return value; +} +``` + +### Expected Output (TypeScript) +```javascript +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.test = test; +// No require() statement +function test(value) { + return value; +} +``` + +## Key Finding + +The bug ONLY occurs when ALL of the following conditions are met: +1. The import statement contains both a `type` import specifier and a regular import specifier +2. The regular import specifier is ONLY used in type positions (not runtime positions) + +### Working Cases (No Bug) +```typescript +// Case 1: Only regular import, type-only usage - WORKS! +import { Value } from "./provider"; +export function test(value: Value): Value { return value; } +// Output: No require() ✓ + +// Case 2: Only type import - WORKS! +import { type Value } from "./provider"; +export function test(value: Value): Value { return value; } +// Output: No require() ✓ +``` + +### Broken Case (Bug) +```typescript +// Both type import and regular import (type-only usage) - BROKEN! +import { Value, type ValueData } from "./provider"; +export function test(value: Value): Value { return value; } +// Output: require("./provider"); ❌ +``` + +## Root Cause Analysis + +### Import Elision Logic +The import elision logic is in `/internal/transformers/tstransforms/importelision.go`: + +1. **KindImportDeclaration** (line 42-55): Visits the import declaration + - If `ImportClause` exists, it visits the import clause + - If the visited import clause is `nil`, the entire import is elided + +2. **KindImportClause** (line 56-64): Processes the import clause + - Checks if default import (`name`) should be emitted via `shouldEmitAliasDeclaration` + - Visits `namedBindings` (which contains the import specifiers) + - If both are `nil`, returns `nil` to elide the entire import + +3. **KindNamedImports** (line 71-78): Processes named imports (`{ Value, type ValueData }`) + - Visits each import specifier + - If all specifiers are elided (empty `elements`), returns `nil` + +4. **KindImportSpecifier** (line 79-84): Processes individual import specifiers + - Calls `shouldEmitAliasDeclaration` to determine if specifier should be kept + - If false, returns `nil` to elide this specifier + +### The `shouldEmitAliasDeclaration` Check +This function (line 130-132) checks: +```go +func (tx *ImportElisionTransformer) shouldEmitAliasDeclaration(node *ast.Node) bool { + return ast.IsInJSFile(node) || tx.isReferencedAliasDeclaration(node) +} +``` + +It calls `isReferencedAliasDeclaration` which checks `aliasLinks.referenced` in the checker. + +### The Bug + +The issue is that when processing an import clause with both type and non-type imports: +1. The `type ValueData` specifier is correctly elided (marked as type-only) +2. The `Value` specifier should also be elided (only used in types) +3. BUT: Something is incorrectly marking `Value` as referenced + +The presence of the `type` import specifier seems to be triggering some code path that marks the non-type import as referenced, even though it's only used in type positions. + +## Investigation Needed + +### Hypothesis +When the import clause contains a mix of `type` and non-`type` imports, there may be: + +1. **Shared alias resolution**: The import clause might be sharing some state or resolution logic that causes the regular import to be marked as referenced when the type import is processed. + +2. **Import clause level check**: There might be a check at the import clause level that marks all non-type imports as referenced if any import is used. + +3. **Symbol merging issue**: The symbols for `Value` and `ValueData` might be getting merged or confused, causing the reference tracking to conflate them. + +### Next Steps + +1. **Add debugging**: Insert logging in `markAliasSymbolAsReferenced` to trace when and why `Value` is being marked as referenced. + +2. **Check import clause processing**: Investigate how `KindImportClause` handles mixed type/non-type imports. + +3. **Symbol resolution**: Check if there's symbol confusion between `Value` (the class) and its usage in type positions. + +4. **Compare with TypeScript**: Look at TypeScript's `checker.ts` to see how it handles this case differently. + +## Files to Investigate + +1. `/internal/transformers/tstransforms/importelision.go` - Import elision transformer +2. `/internal/checker/emitresolver.go` - `IsReferencedAliasDeclaration` implementation +3. `/internal/checker/checker.go` - `markAliasSymbolAsReferenced`, `markAliasReferenced`, etc. +4. `/internal/ast/utilities.go` - `IsPartOfTypeNode` and related functions + +## Test Case for Regression + +Add this test to ensure the fix works: + +```typescript +// @module: commonjs +// @target: es2020 + +// @filename: provider.ts +export class Value { + data: string = ""; +} +export type ValueData = { data: string }; + +// @filename: consumer.ts +import { Value, type ValueData } from "./provider"; + +export function test(value: Value): Value { + return value; +} +``` + +Expected output for `consumer.js` should NOT contain `require("./provider")`. From c27b95b1f20e84d43995d265bc1841710730871a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Feb 2026 00:13:08 +0000 Subject: [PATCH 04/11] Update investigation with root cause analysis Identified that the bug occurs specifically when: - Import statement contains BOTH type-only and non-type specifiers - The non-type specifier is only used in type positions Most likely cause: Import processing logic doesn't properly distinguish between type-level and value-level references when marking aliases as referenced. The presence of a type-only import in the same statement seems to trigger incorrect reference marking for non-type imports. Compared with TypeScript source and found that tsgo is missing logic in markSymbolOfAliasDeclarationIfTypeOnly to check if alias targets resolve to type-only declarations. --- INVESTIGATION.md | 50 +++++++++++++++++++++++++++++++++++------------- 1 file changed, 37 insertions(+), 13 deletions(-) diff --git a/INVESTIGATION.md b/INVESTIGATION.md index 50ec1abd2d9..46cdc54498d 100644 --- a/INVESTIGATION.md +++ b/INVESTIGATION.md @@ -79,6 +79,8 @@ export function test(value: Value): Value { return value; } ## Root Cause Analysis +## Root Cause Analysis + ### Import Elision Logic The import elision logic is in `/internal/transformers/tstransforms/importelision.go`: @@ -114,30 +116,52 @@ It calls `isReferencedAliasDeclaration` which checks `aliasLinks.referenced` in The issue is that when processing an import clause with both type and non-type imports: 1. The `type ValueData` specifier is correctly elided (marked as type-only) 2. The `Value` specifier should also be elided (only used in types) -3. BUT: Something is incorrectly marking `Value` as referenced +3. BUT: `Value`'s `aliasLinks.referenced` is incorrectly being set to `true` + +The presence of the `type` import specifier in the same import statement seems to be triggering some code path that marks the non-type import as referenced, even though it's only used in type positions. + +### Comparison with TypeScript -The presence of the `type` import specifier seems to be triggering some code path that marks the non-type import as referenced, even though it's only used in type positions. +TypeScript's `markSymbolOfAliasDeclarationIfTypeOnly` function (in `checker.ts` around line 4385) has additional parameters `immediateTarget` and `finalTarget` and includes logic to check if the target resolves to a type-only declaration: + +```typescript +function markSymbolOfAliasDeclarationIfTypeOnlyWorker( + aliasDeclarationLinks: SymbolLinks, + target: Symbol | undefined, + overwriteEmpty: boolean +): boolean { + if (target && (aliasDeclarationLinks.typeOnlyDeclaration === undefined || overwriteEmpty && aliasDeclarationLinks.typeOnlyDeclaration === false)) { + const exportSymbol = target.exports?.get(InternalSymbolName.ExportEquals) ?? target; + const typeOnly = exportSymbol.declarations && find(exportSymbol.declarations, isTypeOnlyImportOrExportDeclaration); + aliasDeclarationLinks.typeOnlyDeclaration = typeOnly ?? getSymbolLinks(exportSymbol).typeOnlyDeclaration ?? false; + } + return !!aliasDeclarationLinks.typeOnlyDeclaration; +} +``` -## Investigation Needed +The tsgo version is missing this additional logic to check if the alias target is type-only. -### Hypothesis -When the import clause contains a mix of `type` and non-`type` imports, there may be: +## Hypothesis -1. **Shared alias resolution**: The import clause might be sharing some state or resolution logic that causes the regular import to be marked as referenced when the type import is processed. +There are two possible root causes: -2. **Import clause level check**: There might be a check at the import clause level that marks all non-type imports as referenced if any import is used. +### Hypothesis 1: Missing Type-Only Target Check +The `markSymbolOfAliasDeclarationIfTypeOnly` function in tsgo is missing logic to check if the alias target resolves to a type-only declaration. When `Value` is imported alongside `type ValueData`, the function should check if all uses of `Value` are in type-only positions, but this check is not implemented. -3. **Symbol merging issue**: The symbols for `Value` and `ValueData` might be getting merged or confused, causing the reference tracking to conflate them. +**Evidence**: TypeScript's version has `markSymbolOfAliasDeclarationIfTypeOnlyWorker` that checks the target symbol's declarations for type-only status. -### Next Steps +### Hypothesis 2: Incorrect Reference Marking +When type references are resolved (via `resolveEntityName` called from `getTypeFromTypeReference`), something is incorrectly marking the import alias as referenced. The code should distinguish between: +- Type-level reference: Should NOT mark `aliasLinks.referenced = true` +- Value-level reference: Should mark `aliasLinks.referenced = true` -1. **Add debugging**: Insert logging in `markAliasSymbolAsReferenced` to trace when and why `Value` is being marked as referenced. +**Evidence**: The import works correctly when there's NO `type` import in the same statement, suggesting that the presence of the type-only import is affecting how the non-type import is processed. -2. **Check import clause processing**: Investigate how `KindImportClause` handles mixed type/non-type imports. +## Most Likely Cause -3. **Symbol resolution**: Check if there's symbol confusion between `Value` (the class) and its usage in type positions. +Based on the investigation, **Hypothesis 2** appears most likely. The bug manifests ONLY when there's a mix of type-only and non-type-only imports in the same import statement, which suggests that something in the import processing logic is incorrectly treating all non-type-only imports as referenced when any import from that module is used (even if only in type positions). -4. **Compare with TypeScript**: Look at TypeScript's `checker.ts` to see how it handles this case differently. +The reference marking likely happens during the initial checking of the import declaration or during binding, where the code doesn't properly distinguish between imports that will be used at runtime vs imports that will only be used in type positions. ## Files to Investigate From 2c64cc5ab9f8239598c538783f7d1866108c0a8a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Feb 2026 00:13:47 +0000 Subject: [PATCH 05/11] Add minimal reproduction test case for import elision bug Created testdata/repro-import-elision-bug/ with: - provider.ts: Exports Value class and ValueData type - broken.ts: Demonstrates bug with mixed type/non-type imports - working.ts: Shows correct behavior without type import - README.md: Instructions for reproducing the bug The broken.ts file imports { Value, type ValueData } where Value is only used in type positions, and incorrectly emits require(). --- testdata/repro-import-elision-bug/README.md | 47 +++++++++++++++++++ testdata/repro-import-elision-bug/broken.ts | 5 ++ testdata/repro-import-elision-bug/provider.ts | 4 ++ testdata/repro-import-elision-bug/working.ts | 5 ++ 4 files changed, 61 insertions(+) create mode 100644 testdata/repro-import-elision-bug/README.md create mode 100644 testdata/repro-import-elision-bug/broken.ts create mode 100644 testdata/repro-import-elision-bug/provider.ts create mode 100644 testdata/repro-import-elision-bug/working.ts diff --git a/testdata/repro-import-elision-bug/README.md b/testdata/repro-import-elision-bug/README.md new file mode 100644 index 00000000000..8ee8282cb7e --- /dev/null +++ b/testdata/repro-import-elision-bug/README.md @@ -0,0 +1,47 @@ +# Minimal Reproduction for Import Elision Bug + +This directory contains a minimal test case that reproduces the bug where `tsgo` +incorrectly emits a `require()` statement for type-only imports. + +## Bug Description +When an import statement contains: +1. A `type` import specifier (e.g., `type ValueData`) +2. A regular import specifier that is ONLY used in type positions (e.g., `Value`) + +tsgo incorrectly emits `require()` for the module, even though no runtime import is needed. + +## Test Files +- `provider.ts` - Exports both a class (`Value`) and a type (`ValueData`) +- `broken.ts` - Imports both with and without `type` keyword - **BUG: emits require()** +- `working.ts` - Imports only `Value` without `type` keyword - **Works correctly** + +## How to Reproduce + +```bash +cd testdata/repro-import-elision-bug + +# Compile the broken case +tsgo --module commonjs --target es2020 broken.ts +cat broken.js +# BUG: Contains `require("./provider");` + +# Compile the working case +tsgo --module commonjs --target es2020 working.ts +cat working.js +# CORRECT: No require() statement + +# Compare with TypeScript compiler +tsc --module commonjs --target es2020 broken.ts +cat broken.js +# CORRECT: TypeScript does NOT emit require() +``` + +## Expected Behavior +Neither `broken.ts` nor `working.ts` should emit `require("./provider")` because +`Value` is only used in type annotations, not at runtime. + +## Actual Behavior +- `working.ts`: Correctly does NOT emit `require()` ✓ +- `broken.ts`: Incorrectly DOES emit `require()` ✗ + +The presence of the `type ValueData` import causes the bug to manifest. diff --git a/testdata/repro-import-elision-bug/broken.ts b/testdata/repro-import-elision-bug/broken.ts new file mode 100644 index 00000000000..bbb0b9038f0 --- /dev/null +++ b/testdata/repro-import-elision-bug/broken.ts @@ -0,0 +1,5 @@ +import { Value, type ValueData } from "./provider"; + +export function test(value: Value): Value { + return null as any; +} diff --git a/testdata/repro-import-elision-bug/provider.ts b/testdata/repro-import-elision-bug/provider.ts new file mode 100644 index 00000000000..9a1c3f135c3 --- /dev/null +++ b/testdata/repro-import-elision-bug/provider.ts @@ -0,0 +1,4 @@ +export class Value { + data: string = ""; +} +export type ValueData = { data: string }; diff --git a/testdata/repro-import-elision-bug/working.ts b/testdata/repro-import-elision-bug/working.ts new file mode 100644 index 00000000000..518d71cab18 --- /dev/null +++ b/testdata/repro-import-elision-bug/working.ts @@ -0,0 +1,5 @@ +import { Value } from "./provider"; + +export function test(value: Value): Value { + return null as any; +} From d3ac5f39ec2ae4bf5b8b2674b699996104d06fc7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Feb 2026 00:14:25 +0000 Subject: [PATCH 06/11] Add investigation summary document Concise summary of the import elision bug investigation including: - Clear reproduction steps - Root cause analysis - Key findings and trigger conditions - Next steps for implementing a fix - References to relevant code and test files --- INVESTIGATION_SUMMARY.md | 74 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 INVESTIGATION_SUMMARY.md diff --git a/INVESTIGATION_SUMMARY.md b/INVESTIGATION_SUMMARY.md new file mode 100644 index 00000000000..08aa31ed11c --- /dev/null +++ b/INVESTIGATION_SUMMARY.md @@ -0,0 +1,74 @@ +# Investigation Summary: Import Elision Bug in tsgo + +## Issue +`tsgo` incorrectly emits `require()` statements when an ES6 import contains both: +1. A `type` import specifier (e.g., `type ValueData`) +2. A regular import specifier that is ONLY used in type positions (e.g., `Value`) + +## Reproduction +```typescript +// provider.ts +export class Value { data: string = ""; } +export type ValueData = { data: string }; + +// broken.ts - BUG +import { Value, type ValueData } from "./provider"; +export function test(value: Value): Value { return null as any; } +// Output: require("./provider"); ❌ INCORRECT + +// working.ts - OK +import { Value } from "./provider"; +export function test(value: Value): Value { return null as any; } +// Output: No require() ✓ CORRECT +``` + +## Key Findings + +### Trigger Condition +The bug ONLY occurs when: +- Import statement has BOTH `type` and non-`type` specifiers +- The non-`type` specifier is used ONLY in type positions (not at runtime) + +### Root Cause +The import processing logic incorrectly marks the non-type import's `aliasLinks.referenced` +as `true` when it should remain `false`. This happens during import declaration checking, +where the code fails to distinguish between: +- **Type-level references**: Should NOT mark alias as referenced +- **Value-level references**: Should mark alias as referenced + +### Code Flow +1. Import specifier processed by `ImportElisionTransformer.visit` (importelision.go:79-84) +2. Calls `shouldEmitAliasDeclaration` which checks `isReferencedAliasDeclaration` +3. `isReferencedAliasDeclaration` returns `true` because `aliasLinks.referenced` is `true` +4. Import specifier is NOT elided, causing `require()` to be emitted + +### Missing Logic +Comparison with TypeScript shows that tsgo's `markSymbolOfAliasDeclarationIfTypeOnly` +(checker.go:14701) is missing logic to: +1. Check if the alias target resolves to a type-only declaration +2. Properly handle mixed type/non-type imports in the same statement + +TypeScript has `markSymbolOfAliasDeclarationIfTypeOnlyWorker` that checks the target +symbol's declarations for type-only status, which tsgo is missing. + +## Investigation Files +- `/INVESTIGATION.md` - Detailed investigation notes +- `/testdata/repro-import-elision-bug/` - Minimal reproduction case +- `/testdata/tests/cases/compiler/importValueOnlyUsedAsType.ts` - Original test case + +## Next Steps for Fix +1. Enhance `markSymbolOfAliasDeclarationIfTypeOnly` to check alias targets +2. Ensure type-level references don't set `aliasLinks.referenced = true` +3. Add logic to handle mixed type/non-type imports correctly +4. Ensure the fix doesn't break existing functionality +5. Run all tests to verify no regressions + +## Files to Modify +- `/internal/checker/checker.go` - `markSymbolOfAliasDeclarationIfTypeOnly` and related functions +- Possibly `/internal/checker/emitresolver.go` - `IsReferencedAliasDeclaration` +- Possibly `/internal/transformers/tstransforms/importelision.go` - Import elision logic + +## References +- TypeScript source: `src/compiler/checker.ts` lines 4385-4450 +- Test case: `testdata/tests/cases/compiler/importValueOnlyUsedAsType.ts` +- Repro case: `testdata/repro-import-elision-bug/` From 2581d1b1c031fe1b03017b1b13cd131241c789c1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Feb 2026 00:36:21 +0000 Subject: [PATCH 07/11] Add IsTypeOnly check for import specifiers in import elision transformer Co-authored-by: jakebailey <5341706+jakebailey@users.noreply.github.com> --- INVESTIGATION.md | 195 ------------------ INVESTIGATION_SUMMARY.md | 74 ------- .../tstransforms/importelision.go | 2 +- testdata/repro-import-elision-bug/README.md | 47 ----- testdata/repro-import-elision-bug/broken.ts | 5 - testdata/repro-import-elision-bug/provider.ts | 4 - testdata/repro-import-elision-bug/working.ts | 5 - 7 files changed, 1 insertion(+), 331 deletions(-) delete mode 100644 INVESTIGATION.md delete mode 100644 INVESTIGATION_SUMMARY.md delete mode 100644 testdata/repro-import-elision-bug/README.md delete mode 100644 testdata/repro-import-elision-bug/broken.ts delete mode 100644 testdata/repro-import-elision-bug/provider.ts delete mode 100644 testdata/repro-import-elision-bug/working.ts diff --git a/INVESTIGATION.md b/INVESTIGATION.md deleted file mode 100644 index 46cdc54498d..00000000000 --- a/INVESTIGATION.md +++ /dev/null @@ -1,195 +0,0 @@ -# Investigation: Incorrect `require` Emission for Type-Only Imports - -## Problem Summary -`tsgo` incorrectly emits a `require` statement when an ES6 import contains: -1. A `type` import specifier (e.g., `type ValueData`) -2. A regular import specifier (e.g., `Value`) that is ONLY used in type positions - -## Reproduction - -### Minimal Test Case -```typescript -// provider.ts -export class Value { - data: string = ""; -} -export type ValueData = { data: string }; - -// consumer.ts - BROKEN -import { Value, type ValueData } from "./provider"; -export function test(value: Value): Value { - return null as any; -} - -// consumer-alt.ts - WORKS -import { Value } from "./provider"; -export function test(value: Value): Value { - return null as any; -} -``` - -### Actual Output (tsgo) -```javascript -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.test = test; -require("./provider"); // ❌ THIS SHOULD NOT BE HERE -function test(value) { - return value; -} -``` - -### Expected Output (TypeScript) -```javascript -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.test = test; -// No require() statement -function test(value) { - return value; -} -``` - -## Key Finding - -The bug ONLY occurs when ALL of the following conditions are met: -1. The import statement contains both a `type` import specifier and a regular import specifier -2. The regular import specifier is ONLY used in type positions (not runtime positions) - -### Working Cases (No Bug) -```typescript -// Case 1: Only regular import, type-only usage - WORKS! -import { Value } from "./provider"; -export function test(value: Value): Value { return value; } -// Output: No require() ✓ - -// Case 2: Only type import - WORKS! -import { type Value } from "./provider"; -export function test(value: Value): Value { return value; } -// Output: No require() ✓ -``` - -### Broken Case (Bug) -```typescript -// Both type import and regular import (type-only usage) - BROKEN! -import { Value, type ValueData } from "./provider"; -export function test(value: Value): Value { return value; } -// Output: require("./provider"); ❌ -``` - -## Root Cause Analysis - -## Root Cause Analysis - -### Import Elision Logic -The import elision logic is in `/internal/transformers/tstransforms/importelision.go`: - -1. **KindImportDeclaration** (line 42-55): Visits the import declaration - - If `ImportClause` exists, it visits the import clause - - If the visited import clause is `nil`, the entire import is elided - -2. **KindImportClause** (line 56-64): Processes the import clause - - Checks if default import (`name`) should be emitted via `shouldEmitAliasDeclaration` - - Visits `namedBindings` (which contains the import specifiers) - - If both are `nil`, returns `nil` to elide the entire import - -3. **KindNamedImports** (line 71-78): Processes named imports (`{ Value, type ValueData }`) - - Visits each import specifier - - If all specifiers are elided (empty `elements`), returns `nil` - -4. **KindImportSpecifier** (line 79-84): Processes individual import specifiers - - Calls `shouldEmitAliasDeclaration` to determine if specifier should be kept - - If false, returns `nil` to elide this specifier - -### The `shouldEmitAliasDeclaration` Check -This function (line 130-132) checks: -```go -func (tx *ImportElisionTransformer) shouldEmitAliasDeclaration(node *ast.Node) bool { - return ast.IsInJSFile(node) || tx.isReferencedAliasDeclaration(node) -} -``` - -It calls `isReferencedAliasDeclaration` which checks `aliasLinks.referenced` in the checker. - -### The Bug - -The issue is that when processing an import clause with both type and non-type imports: -1. The `type ValueData` specifier is correctly elided (marked as type-only) -2. The `Value` specifier should also be elided (only used in types) -3. BUT: `Value`'s `aliasLinks.referenced` is incorrectly being set to `true` - -The presence of the `type` import specifier in the same import statement seems to be triggering some code path that marks the non-type import as referenced, even though it's only used in type positions. - -### Comparison with TypeScript - -TypeScript's `markSymbolOfAliasDeclarationIfTypeOnly` function (in `checker.ts` around line 4385) has additional parameters `immediateTarget` and `finalTarget` and includes logic to check if the target resolves to a type-only declaration: - -```typescript -function markSymbolOfAliasDeclarationIfTypeOnlyWorker( - aliasDeclarationLinks: SymbolLinks, - target: Symbol | undefined, - overwriteEmpty: boolean -): boolean { - if (target && (aliasDeclarationLinks.typeOnlyDeclaration === undefined || overwriteEmpty && aliasDeclarationLinks.typeOnlyDeclaration === false)) { - const exportSymbol = target.exports?.get(InternalSymbolName.ExportEquals) ?? target; - const typeOnly = exportSymbol.declarations && find(exportSymbol.declarations, isTypeOnlyImportOrExportDeclaration); - aliasDeclarationLinks.typeOnlyDeclaration = typeOnly ?? getSymbolLinks(exportSymbol).typeOnlyDeclaration ?? false; - } - return !!aliasDeclarationLinks.typeOnlyDeclaration; -} -``` - -The tsgo version is missing this additional logic to check if the alias target is type-only. - -## Hypothesis - -There are two possible root causes: - -### Hypothesis 1: Missing Type-Only Target Check -The `markSymbolOfAliasDeclarationIfTypeOnly` function in tsgo is missing logic to check if the alias target resolves to a type-only declaration. When `Value` is imported alongside `type ValueData`, the function should check if all uses of `Value` are in type-only positions, but this check is not implemented. - -**Evidence**: TypeScript's version has `markSymbolOfAliasDeclarationIfTypeOnlyWorker` that checks the target symbol's declarations for type-only status. - -### Hypothesis 2: Incorrect Reference Marking -When type references are resolved (via `resolveEntityName` called from `getTypeFromTypeReference`), something is incorrectly marking the import alias as referenced. The code should distinguish between: -- Type-level reference: Should NOT mark `aliasLinks.referenced = true` -- Value-level reference: Should mark `aliasLinks.referenced = true` - -**Evidence**: The import works correctly when there's NO `type` import in the same statement, suggesting that the presence of the type-only import is affecting how the non-type import is processed. - -## Most Likely Cause - -Based on the investigation, **Hypothesis 2** appears most likely. The bug manifests ONLY when there's a mix of type-only and non-type-only imports in the same import statement, which suggests that something in the import processing logic is incorrectly treating all non-type-only imports as referenced when any import from that module is used (even if only in type positions). - -The reference marking likely happens during the initial checking of the import declaration or during binding, where the code doesn't properly distinguish between imports that will be used at runtime vs imports that will only be used in type positions. - -## Files to Investigate - -1. `/internal/transformers/tstransforms/importelision.go` - Import elision transformer -2. `/internal/checker/emitresolver.go` - `IsReferencedAliasDeclaration` implementation -3. `/internal/checker/checker.go` - `markAliasSymbolAsReferenced`, `markAliasReferenced`, etc. -4. `/internal/ast/utilities.go` - `IsPartOfTypeNode` and related functions - -## Test Case for Regression - -Add this test to ensure the fix works: - -```typescript -// @module: commonjs -// @target: es2020 - -// @filename: provider.ts -export class Value { - data: string = ""; -} -export type ValueData = { data: string }; - -// @filename: consumer.ts -import { Value, type ValueData } from "./provider"; - -export function test(value: Value): Value { - return value; -} -``` - -Expected output for `consumer.js` should NOT contain `require("./provider")`. diff --git a/INVESTIGATION_SUMMARY.md b/INVESTIGATION_SUMMARY.md deleted file mode 100644 index 08aa31ed11c..00000000000 --- a/INVESTIGATION_SUMMARY.md +++ /dev/null @@ -1,74 +0,0 @@ -# Investigation Summary: Import Elision Bug in tsgo - -## Issue -`tsgo` incorrectly emits `require()` statements when an ES6 import contains both: -1. A `type` import specifier (e.g., `type ValueData`) -2. A regular import specifier that is ONLY used in type positions (e.g., `Value`) - -## Reproduction -```typescript -// provider.ts -export class Value { data: string = ""; } -export type ValueData = { data: string }; - -// broken.ts - BUG -import { Value, type ValueData } from "./provider"; -export function test(value: Value): Value { return null as any; } -// Output: require("./provider"); ❌ INCORRECT - -// working.ts - OK -import { Value } from "./provider"; -export function test(value: Value): Value { return null as any; } -// Output: No require() ✓ CORRECT -``` - -## Key Findings - -### Trigger Condition -The bug ONLY occurs when: -- Import statement has BOTH `type` and non-`type` specifiers -- The non-`type` specifier is used ONLY in type positions (not at runtime) - -### Root Cause -The import processing logic incorrectly marks the non-type import's `aliasLinks.referenced` -as `true` when it should remain `false`. This happens during import declaration checking, -where the code fails to distinguish between: -- **Type-level references**: Should NOT mark alias as referenced -- **Value-level references**: Should mark alias as referenced - -### Code Flow -1. Import specifier processed by `ImportElisionTransformer.visit` (importelision.go:79-84) -2. Calls `shouldEmitAliasDeclaration` which checks `isReferencedAliasDeclaration` -3. `isReferencedAliasDeclaration` returns `true` because `aliasLinks.referenced` is `true` -4. Import specifier is NOT elided, causing `require()` to be emitted - -### Missing Logic -Comparison with TypeScript shows that tsgo's `markSymbolOfAliasDeclarationIfTypeOnly` -(checker.go:14701) is missing logic to: -1. Check if the alias target resolves to a type-only declaration -2. Properly handle mixed type/non-type imports in the same statement - -TypeScript has `markSymbolOfAliasDeclarationIfTypeOnlyWorker` that checks the target -symbol's declarations for type-only status, which tsgo is missing. - -## Investigation Files -- `/INVESTIGATION.md` - Detailed investigation notes -- `/testdata/repro-import-elision-bug/` - Minimal reproduction case -- `/testdata/tests/cases/compiler/importValueOnlyUsedAsType.ts` - Original test case - -## Next Steps for Fix -1. Enhance `markSymbolOfAliasDeclarationIfTypeOnly` to check alias targets -2. Ensure type-level references don't set `aliasLinks.referenced = true` -3. Add logic to handle mixed type/non-type imports correctly -4. Ensure the fix doesn't break existing functionality -5. Run all tests to verify no regressions - -## Files to Modify -- `/internal/checker/checker.go` - `markSymbolOfAliasDeclarationIfTypeOnly` and related functions -- Possibly `/internal/checker/emitresolver.go` - `IsReferencedAliasDeclaration` -- Possibly `/internal/transformers/tstransforms/importelision.go` - Import elision logic - -## References -- TypeScript source: `src/compiler/checker.ts` lines 4385-4450 -- Test case: `testdata/tests/cases/compiler/importValueOnlyUsedAsType.ts` -- Repro case: `testdata/repro-import-elision-bug/` diff --git a/internal/transformers/tstransforms/importelision.go b/internal/transformers/tstransforms/importelision.go index 7b23343d4a7..52106899fea 100644 --- a/internal/transformers/tstransforms/importelision.go +++ b/internal/transformers/tstransforms/importelision.go @@ -77,7 +77,7 @@ func (tx *ImportElisionTransformer) visit(node *ast.Node) *ast.Node { } return tx.Factory().UpdateNamedImports(n, elements) case ast.KindImportSpecifier: - if !tx.shouldEmitAliasDeclaration(node) { + if node.IsTypeOnly() || !tx.shouldEmitAliasDeclaration(node) { // elide type-only or unused imports return nil } diff --git a/testdata/repro-import-elision-bug/README.md b/testdata/repro-import-elision-bug/README.md deleted file mode 100644 index 8ee8282cb7e..00000000000 --- a/testdata/repro-import-elision-bug/README.md +++ /dev/null @@ -1,47 +0,0 @@ -# Minimal Reproduction for Import Elision Bug - -This directory contains a minimal test case that reproduces the bug where `tsgo` -incorrectly emits a `require()` statement for type-only imports. - -## Bug Description -When an import statement contains: -1. A `type` import specifier (e.g., `type ValueData`) -2. A regular import specifier that is ONLY used in type positions (e.g., `Value`) - -tsgo incorrectly emits `require()` for the module, even though no runtime import is needed. - -## Test Files -- `provider.ts` - Exports both a class (`Value`) and a type (`ValueData`) -- `broken.ts` - Imports both with and without `type` keyword - **BUG: emits require()** -- `working.ts` - Imports only `Value` without `type` keyword - **Works correctly** - -## How to Reproduce - -```bash -cd testdata/repro-import-elision-bug - -# Compile the broken case -tsgo --module commonjs --target es2020 broken.ts -cat broken.js -# BUG: Contains `require("./provider");` - -# Compile the working case -tsgo --module commonjs --target es2020 working.ts -cat working.js -# CORRECT: No require() statement - -# Compare with TypeScript compiler -tsc --module commonjs --target es2020 broken.ts -cat broken.js -# CORRECT: TypeScript does NOT emit require() -``` - -## Expected Behavior -Neither `broken.ts` nor `working.ts` should emit `require("./provider")` because -`Value` is only used in type annotations, not at runtime. - -## Actual Behavior -- `working.ts`: Correctly does NOT emit `require()` ✓ -- `broken.ts`: Incorrectly DOES emit `require()` ✗ - -The presence of the `type ValueData` import causes the bug to manifest. diff --git a/testdata/repro-import-elision-bug/broken.ts b/testdata/repro-import-elision-bug/broken.ts deleted file mode 100644 index bbb0b9038f0..00000000000 --- a/testdata/repro-import-elision-bug/broken.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { Value, type ValueData } from "./provider"; - -export function test(value: Value): Value { - return null as any; -} diff --git a/testdata/repro-import-elision-bug/provider.ts b/testdata/repro-import-elision-bug/provider.ts deleted file mode 100644 index 9a1c3f135c3..00000000000 --- a/testdata/repro-import-elision-bug/provider.ts +++ /dev/null @@ -1,4 +0,0 @@ -export class Value { - data: string = ""; -} -export type ValueData = { data: string }; diff --git a/testdata/repro-import-elision-bug/working.ts b/testdata/repro-import-elision-bug/working.ts deleted file mode 100644 index 518d71cab18..00000000000 --- a/testdata/repro-import-elision-bug/working.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { Value } from "./provider"; - -export function test(value: Value): Value { - return null as any; -} From e6241280caa3e080bdd0a8167b4d0a4768c2b05a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Feb 2026 18:30:15 +0000 Subject: [PATCH 08/11] Fix import elision when import clause was modified by previous transformers When type-only import specifiers (like `type ValueData`) are present, the TypeEraserTransformer modifies the import clause before the ImportElisionTransformer runs. This caused isElisionBlocked to return true, preventing proper elision of the import declaration. The fix ensures that: 1. The import clause is always visited first 2. If the import clause becomes nil (all bindings elided), the import declaration is elided regardless of isElisionBlocked 3. If the import clause changed, the import declaration is updated This makes tsgo match TypeScript's behavior, where imports only used in type positions are properly elided. Co-authored-by: jakebailey <5341706+jakebailey@users.noreply.github.com> --- .../tstransforms/importelision.go | 23 +++++++++++-------- .../compiler/importValueOnlyUsedAsType.js | 2 -- .../conformance/importSpecifiers1.js | 2 +- .../conformance/importSpecifiers1.js.diff | 11 --------- ...odeDeclarationEmitErrors(module=node16).js | 2 -- ...clarationEmitErrors(module=node16).js.diff | 11 --------- ...odeDeclarationEmitErrors(module=node18).js | 2 -- ...clarationEmitErrors(module=node18).js.diff | 11 --------- ...odeDeclarationEmitErrors(module=node20).js | 2 -- ...clarationEmitErrors(module=node20).js.diff | 11 --------- ...eDeclarationEmitErrors(module=nodenext).js | 2 -- ...arationEmitErrors(module=nodenext).js.diff | 11 --------- ...deDeclarationEmitErrors1(module=node16).js | 2 -- ...larationEmitErrors1(module=node16).js.diff | 11 --------- ...deDeclarationEmitErrors1(module=node18).js | 2 -- ...larationEmitErrors1(module=node18).js.diff | 11 --------- ...deDeclarationEmitErrors1(module=node20).js | 2 -- ...larationEmitErrors1(module=node20).js.diff | 11 --------- ...DeclarationEmitErrors1(module=nodenext).js | 2 -- ...rationEmitErrors1(module=nodenext).js.diff | 11 --------- 20 files changed, 15 insertions(+), 127 deletions(-) delete mode 100644 testdata/baselines/reference/submodule/conformance/importSpecifiers1.js.diff delete mode 100644 testdata/baselines/reference/submodule/conformance/nodeModulesImportAttributesModeDeclarationEmitErrors(module=node16).js.diff delete mode 100644 testdata/baselines/reference/submodule/conformance/nodeModulesImportAttributesModeDeclarationEmitErrors(module=node18).js.diff delete mode 100644 testdata/baselines/reference/submodule/conformance/nodeModulesImportAttributesModeDeclarationEmitErrors(module=node20).js.diff delete mode 100644 testdata/baselines/reference/submodule/conformance/nodeModulesImportAttributesModeDeclarationEmitErrors(module=nodenext).js.diff delete mode 100644 testdata/baselines/reference/submodule/conformance/nodeModulesImportModeDeclarationEmitErrors1(module=node16).js.diff delete mode 100644 testdata/baselines/reference/submodule/conformance/nodeModulesImportModeDeclarationEmitErrors1(module=node18).js.diff delete mode 100644 testdata/baselines/reference/submodule/conformance/nodeModulesImportModeDeclarationEmitErrors1(module=node20).js.diff delete mode 100644 testdata/baselines/reference/submodule/conformance/nodeModulesImportModeDeclarationEmitErrors1(module=nodenext).js.diff diff --git a/internal/transformers/tstransforms/importelision.go b/internal/transformers/tstransforms/importelision.go index 52106899fea..3ff144138ab 100644 --- a/internal/transformers/tstransforms/importelision.go +++ b/internal/transformers/tstransforms/importelision.go @@ -40,15 +40,20 @@ func (tx *ImportElisionTransformer) visit(node *ast.Node) *ast.Node { } return tx.Visitor().VisitEachChild(node) case ast.KindImportDeclaration: - if !tx.isElisionBlocked(node) { - n := node.AsImportDeclaration() - // Do not elide a side-effect only import declaration. - // import "foo"; - if n.ImportClause != nil { - importClause := tx.Visitor().VisitNode(n.ImportClause) - if importClause == nil { - return nil - } + n := node.AsImportDeclaration() + // Do not elide a side-effect only import declaration. + // import "foo"; + if n.ImportClause != nil { + importClause := tx.Visitor().VisitNode(n.ImportClause) + if importClause == nil { + // All import bindings were elided, so elide the import declaration + return nil + } + if !tx.isElisionBlocked(node) { + return tx.Factory().UpdateImportDeclaration(n, n.Modifiers(), importClause, n.ModuleSpecifier, tx.Visitor().VisitNode(n.Attributes)) + } + // If elision is blocked but importClause changed, we still need to update + if importClause != n.ImportClause { return tx.Factory().UpdateImportDeclaration(n, n.Modifiers(), importClause, n.ModuleSpecifier, tx.Visitor().VisitNode(n.Attributes)) } } diff --git a/testdata/baselines/reference/compiler/importValueOnlyUsedAsType.js b/testdata/baselines/reference/compiler/importValueOnlyUsedAsType.js index 5e69683855a..07246cc4cf0 100644 --- a/testdata/baselines/reference/compiler/importValueOnlyUsedAsType.js +++ b/testdata/baselines/reference/compiler/importValueOnlyUsedAsType.js @@ -44,8 +44,6 @@ exports.Value = Value; Object.defineProperty(exports, "__esModule", { value: true }); exports.BaseProcessor = void 0; exports.processRecord = processRecord; -// Import Value as a value (no `type` keyword), but only use it in type positions -require("./provider"); function processRecord(value, // parameter type callback) { // return type diff --git a/testdata/baselines/reference/submodule/conformance/importSpecifiers1.js b/testdata/baselines/reference/submodule/conformance/importSpecifiers1.js index 46f834fbebe..71db89bcbb4 100644 --- a/testdata/baselines/reference/submodule/conformance/importSpecifiers1.js +++ b/testdata/baselines/reference/submodule/conformance/importSpecifiers1.js @@ -62,7 +62,7 @@ type; // Error (cannot resolve name) as; // Error (used in emitting position) export {}; //// [d.js] -import "./mod.js"; // Error +export {}; //// [e.js] import { type as type } from "./mod.js"; type; diff --git a/testdata/baselines/reference/submodule/conformance/importSpecifiers1.js.diff b/testdata/baselines/reference/submodule/conformance/importSpecifiers1.js.diff deleted file mode 100644 index 3031ffe9e73..00000000000 --- a/testdata/baselines/reference/submodule/conformance/importSpecifiers1.js.diff +++ /dev/null @@ -1,11 +0,0 @@ ---- old.importSpecifiers1.js -+++ new.importSpecifiers1.js -@@= skipped -61, +61 lines =@@ - as; // Error (used in emitting position) - export {}; - //// [d.js] --export {}; -+import "./mod.js"; // Error - //// [e.js] - import { type as type } from "./mod.js"; - type; \ No newline at end of file diff --git a/testdata/baselines/reference/submodule/conformance/nodeModulesImportAttributesModeDeclarationEmitErrors(module=node16).js b/testdata/baselines/reference/submodule/conformance/nodeModulesImportAttributesModeDeclarationEmitErrors(module=node16).js index 9d1ac266495..988072b042c 100644 --- a/testdata/baselines/reference/submodule/conformance/nodeModulesImportAttributesModeDeclarationEmitErrors(module=node16).js +++ b/testdata/baselines/reference/submodule/conformance/nodeModulesImportAttributesModeDeclarationEmitErrors(module=node16).js @@ -30,8 +30,6 @@ export interface LocalInterface extends RequireInterface, ImportInterface {} //// [index.js] "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -// not exclusively type-only -require("pkg"); //// [index.d.ts] diff --git a/testdata/baselines/reference/submodule/conformance/nodeModulesImportAttributesModeDeclarationEmitErrors(module=node16).js.diff b/testdata/baselines/reference/submodule/conformance/nodeModulesImportAttributesModeDeclarationEmitErrors(module=node16).js.diff deleted file mode 100644 index d8de5a5e71b..00000000000 --- a/testdata/baselines/reference/submodule/conformance/nodeModulesImportAttributesModeDeclarationEmitErrors(module=node16).js.diff +++ /dev/null @@ -1,11 +0,0 @@ ---- old.nodeModulesImportAttributesModeDeclarationEmitErrors(module=node16).js -+++ new.nodeModulesImportAttributesModeDeclarationEmitErrors(module=node16).js -@@= skipped -29, +29 lines =@@ - //// [index.js] - "use strict"; - Object.defineProperty(exports, "__esModule", { value: true }); -+// not exclusively type-only -+require("pkg"); - - - //// [index.d.ts] \ No newline at end of file diff --git a/testdata/baselines/reference/submodule/conformance/nodeModulesImportAttributesModeDeclarationEmitErrors(module=node18).js b/testdata/baselines/reference/submodule/conformance/nodeModulesImportAttributesModeDeclarationEmitErrors(module=node18).js index 9d1ac266495..988072b042c 100644 --- a/testdata/baselines/reference/submodule/conformance/nodeModulesImportAttributesModeDeclarationEmitErrors(module=node18).js +++ b/testdata/baselines/reference/submodule/conformance/nodeModulesImportAttributesModeDeclarationEmitErrors(module=node18).js @@ -30,8 +30,6 @@ export interface LocalInterface extends RequireInterface, ImportInterface {} //// [index.js] "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -// not exclusively type-only -require("pkg"); //// [index.d.ts] diff --git a/testdata/baselines/reference/submodule/conformance/nodeModulesImportAttributesModeDeclarationEmitErrors(module=node18).js.diff b/testdata/baselines/reference/submodule/conformance/nodeModulesImportAttributesModeDeclarationEmitErrors(module=node18).js.diff deleted file mode 100644 index b59663c1020..00000000000 --- a/testdata/baselines/reference/submodule/conformance/nodeModulesImportAttributesModeDeclarationEmitErrors(module=node18).js.diff +++ /dev/null @@ -1,11 +0,0 @@ ---- old.nodeModulesImportAttributesModeDeclarationEmitErrors(module=node18).js -+++ new.nodeModulesImportAttributesModeDeclarationEmitErrors(module=node18).js -@@= skipped -29, +29 lines =@@ - //// [index.js] - "use strict"; - Object.defineProperty(exports, "__esModule", { value: true }); -+// not exclusively type-only -+require("pkg"); - - - //// [index.d.ts] \ No newline at end of file diff --git a/testdata/baselines/reference/submodule/conformance/nodeModulesImportAttributesModeDeclarationEmitErrors(module=node20).js b/testdata/baselines/reference/submodule/conformance/nodeModulesImportAttributesModeDeclarationEmitErrors(module=node20).js index 9d1ac266495..988072b042c 100644 --- a/testdata/baselines/reference/submodule/conformance/nodeModulesImportAttributesModeDeclarationEmitErrors(module=node20).js +++ b/testdata/baselines/reference/submodule/conformance/nodeModulesImportAttributesModeDeclarationEmitErrors(module=node20).js @@ -30,8 +30,6 @@ export interface LocalInterface extends RequireInterface, ImportInterface {} //// [index.js] "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -// not exclusively type-only -require("pkg"); //// [index.d.ts] diff --git a/testdata/baselines/reference/submodule/conformance/nodeModulesImportAttributesModeDeclarationEmitErrors(module=node20).js.diff b/testdata/baselines/reference/submodule/conformance/nodeModulesImportAttributesModeDeclarationEmitErrors(module=node20).js.diff deleted file mode 100644 index ebc4ffa1db2..00000000000 --- a/testdata/baselines/reference/submodule/conformance/nodeModulesImportAttributesModeDeclarationEmitErrors(module=node20).js.diff +++ /dev/null @@ -1,11 +0,0 @@ ---- old.nodeModulesImportAttributesModeDeclarationEmitErrors(module=node20).js -+++ new.nodeModulesImportAttributesModeDeclarationEmitErrors(module=node20).js -@@= skipped -29, +29 lines =@@ - //// [index.js] - "use strict"; - Object.defineProperty(exports, "__esModule", { value: true }); -+// not exclusively type-only -+require("pkg"); - - - //// [index.d.ts] \ No newline at end of file diff --git a/testdata/baselines/reference/submodule/conformance/nodeModulesImportAttributesModeDeclarationEmitErrors(module=nodenext).js b/testdata/baselines/reference/submodule/conformance/nodeModulesImportAttributesModeDeclarationEmitErrors(module=nodenext).js index 9d1ac266495..988072b042c 100644 --- a/testdata/baselines/reference/submodule/conformance/nodeModulesImportAttributesModeDeclarationEmitErrors(module=nodenext).js +++ b/testdata/baselines/reference/submodule/conformance/nodeModulesImportAttributesModeDeclarationEmitErrors(module=nodenext).js @@ -30,8 +30,6 @@ export interface LocalInterface extends RequireInterface, ImportInterface {} //// [index.js] "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -// not exclusively type-only -require("pkg"); //// [index.d.ts] diff --git a/testdata/baselines/reference/submodule/conformance/nodeModulesImportAttributesModeDeclarationEmitErrors(module=nodenext).js.diff b/testdata/baselines/reference/submodule/conformance/nodeModulesImportAttributesModeDeclarationEmitErrors(module=nodenext).js.diff deleted file mode 100644 index f07a514c7ec..00000000000 --- a/testdata/baselines/reference/submodule/conformance/nodeModulesImportAttributesModeDeclarationEmitErrors(module=nodenext).js.diff +++ /dev/null @@ -1,11 +0,0 @@ ---- old.nodeModulesImportAttributesModeDeclarationEmitErrors(module=nodenext).js -+++ new.nodeModulesImportAttributesModeDeclarationEmitErrors(module=nodenext).js -@@= skipped -29, +29 lines =@@ - //// [index.js] - "use strict"; - Object.defineProperty(exports, "__esModule", { value: true }); -+// not exclusively type-only -+require("pkg"); - - - //// [index.d.ts] \ No newline at end of file diff --git a/testdata/baselines/reference/submodule/conformance/nodeModulesImportModeDeclarationEmitErrors1(module=node16).js b/testdata/baselines/reference/submodule/conformance/nodeModulesImportModeDeclarationEmitErrors1(module=node16).js index d036a9af601..3b2af2656d0 100644 --- a/testdata/baselines/reference/submodule/conformance/nodeModulesImportModeDeclarationEmitErrors1(module=node16).js +++ b/testdata/baselines/reference/submodule/conformance/nodeModulesImportModeDeclarationEmitErrors1(module=node16).js @@ -30,8 +30,6 @@ export interface LocalInterface extends RequireInterface, ImportInterface {} //// [index.js] "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -// not exclusively type-only -require("pkg"); //// [index.d.ts] diff --git a/testdata/baselines/reference/submodule/conformance/nodeModulesImportModeDeclarationEmitErrors1(module=node16).js.diff b/testdata/baselines/reference/submodule/conformance/nodeModulesImportModeDeclarationEmitErrors1(module=node16).js.diff deleted file mode 100644 index fe0234b6b8b..00000000000 --- a/testdata/baselines/reference/submodule/conformance/nodeModulesImportModeDeclarationEmitErrors1(module=node16).js.diff +++ /dev/null @@ -1,11 +0,0 @@ ---- old.nodeModulesImportModeDeclarationEmitErrors1(module=node16).js -+++ new.nodeModulesImportModeDeclarationEmitErrors1(module=node16).js -@@= skipped -29, +29 lines =@@ - //// [index.js] - "use strict"; - Object.defineProperty(exports, "__esModule", { value: true }); -+// not exclusively type-only -+require("pkg"); - - - //// [index.d.ts] \ No newline at end of file diff --git a/testdata/baselines/reference/submodule/conformance/nodeModulesImportModeDeclarationEmitErrors1(module=node18).js b/testdata/baselines/reference/submodule/conformance/nodeModulesImportModeDeclarationEmitErrors1(module=node18).js index d036a9af601..3b2af2656d0 100644 --- a/testdata/baselines/reference/submodule/conformance/nodeModulesImportModeDeclarationEmitErrors1(module=node18).js +++ b/testdata/baselines/reference/submodule/conformance/nodeModulesImportModeDeclarationEmitErrors1(module=node18).js @@ -30,8 +30,6 @@ export interface LocalInterface extends RequireInterface, ImportInterface {} //// [index.js] "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -// not exclusively type-only -require("pkg"); //// [index.d.ts] diff --git a/testdata/baselines/reference/submodule/conformance/nodeModulesImportModeDeclarationEmitErrors1(module=node18).js.diff b/testdata/baselines/reference/submodule/conformance/nodeModulesImportModeDeclarationEmitErrors1(module=node18).js.diff deleted file mode 100644 index 28700d7fd14..00000000000 --- a/testdata/baselines/reference/submodule/conformance/nodeModulesImportModeDeclarationEmitErrors1(module=node18).js.diff +++ /dev/null @@ -1,11 +0,0 @@ ---- old.nodeModulesImportModeDeclarationEmitErrors1(module=node18).js -+++ new.nodeModulesImportModeDeclarationEmitErrors1(module=node18).js -@@= skipped -29, +29 lines =@@ - //// [index.js] - "use strict"; - Object.defineProperty(exports, "__esModule", { value: true }); -+// not exclusively type-only -+require("pkg"); - - - //// [index.d.ts] \ No newline at end of file diff --git a/testdata/baselines/reference/submodule/conformance/nodeModulesImportModeDeclarationEmitErrors1(module=node20).js b/testdata/baselines/reference/submodule/conformance/nodeModulesImportModeDeclarationEmitErrors1(module=node20).js index d036a9af601..3b2af2656d0 100644 --- a/testdata/baselines/reference/submodule/conformance/nodeModulesImportModeDeclarationEmitErrors1(module=node20).js +++ b/testdata/baselines/reference/submodule/conformance/nodeModulesImportModeDeclarationEmitErrors1(module=node20).js @@ -30,8 +30,6 @@ export interface LocalInterface extends RequireInterface, ImportInterface {} //// [index.js] "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -// not exclusively type-only -require("pkg"); //// [index.d.ts] diff --git a/testdata/baselines/reference/submodule/conformance/nodeModulesImportModeDeclarationEmitErrors1(module=node20).js.diff b/testdata/baselines/reference/submodule/conformance/nodeModulesImportModeDeclarationEmitErrors1(module=node20).js.diff deleted file mode 100644 index f7b38f460c0..00000000000 --- a/testdata/baselines/reference/submodule/conformance/nodeModulesImportModeDeclarationEmitErrors1(module=node20).js.diff +++ /dev/null @@ -1,11 +0,0 @@ ---- old.nodeModulesImportModeDeclarationEmitErrors1(module=node20).js -+++ new.nodeModulesImportModeDeclarationEmitErrors1(module=node20).js -@@= skipped -29, +29 lines =@@ - //// [index.js] - "use strict"; - Object.defineProperty(exports, "__esModule", { value: true }); -+// not exclusively type-only -+require("pkg"); - - - //// [index.d.ts] \ No newline at end of file diff --git a/testdata/baselines/reference/submodule/conformance/nodeModulesImportModeDeclarationEmitErrors1(module=nodenext).js b/testdata/baselines/reference/submodule/conformance/nodeModulesImportModeDeclarationEmitErrors1(module=nodenext).js index d036a9af601..3b2af2656d0 100644 --- a/testdata/baselines/reference/submodule/conformance/nodeModulesImportModeDeclarationEmitErrors1(module=nodenext).js +++ b/testdata/baselines/reference/submodule/conformance/nodeModulesImportModeDeclarationEmitErrors1(module=nodenext).js @@ -30,8 +30,6 @@ export interface LocalInterface extends RequireInterface, ImportInterface {} //// [index.js] "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -// not exclusively type-only -require("pkg"); //// [index.d.ts] diff --git a/testdata/baselines/reference/submodule/conformance/nodeModulesImportModeDeclarationEmitErrors1(module=nodenext).js.diff b/testdata/baselines/reference/submodule/conformance/nodeModulesImportModeDeclarationEmitErrors1(module=nodenext).js.diff deleted file mode 100644 index 4b93d9af954..00000000000 --- a/testdata/baselines/reference/submodule/conformance/nodeModulesImportModeDeclarationEmitErrors1(module=nodenext).js.diff +++ /dev/null @@ -1,11 +0,0 @@ ---- old.nodeModulesImportModeDeclarationEmitErrors1(module=nodenext).js -+++ new.nodeModulesImportModeDeclarationEmitErrors1(module=nodenext).js -@@= skipped -29, +29 lines =@@ - //// [index.js] - "use strict"; - Object.defineProperty(exports, "__esModule", { value: true }); -+// not exclusively type-only -+require("pkg"); - - - //// [index.d.ts] \ No newline at end of file From 3a0ebb7e0925e3cd7db8978c198d4d1b865d4093 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Feb 2026 18:53:26 +0000 Subject: [PATCH 09/11] Remove redundant test case importValueOnlyUsedAsType The test case is covered by other existing submodule tests like importSpecifiers1.ts and nodeModulesImportAttributesModeDeclarationEmitErrors. Co-authored-by: jakebailey <5341706+jakebailey@users.noreply.github.com> --- .../compiler/importValueOnlyUsedAsType.js | 56 --------------- .../importValueOnlyUsedAsType.symbols | 69 ------------------- .../compiler/importValueOnlyUsedAsType.types | 61 ---------------- .../compiler/importValueOnlyUsedAsType.ts | 32 --------- 4 files changed, 218 deletions(-) delete mode 100644 testdata/baselines/reference/compiler/importValueOnlyUsedAsType.js delete mode 100644 testdata/baselines/reference/compiler/importValueOnlyUsedAsType.symbols delete mode 100644 testdata/baselines/reference/compiler/importValueOnlyUsedAsType.types delete mode 100644 testdata/tests/cases/compiler/importValueOnlyUsedAsType.ts diff --git a/testdata/baselines/reference/compiler/importValueOnlyUsedAsType.js b/testdata/baselines/reference/compiler/importValueOnlyUsedAsType.js deleted file mode 100644 index 07246cc4cf0..00000000000 --- a/testdata/baselines/reference/compiler/importValueOnlyUsedAsType.js +++ /dev/null @@ -1,56 +0,0 @@ -//// [tests/cases/compiler/importValueOnlyUsedAsType.ts] //// - -//// [provider.ts] -export class Value { - data: string = ""; -} -export type ValueData = { data: string }; - -//// [consumer.ts] -// Import Value as a value (no `type` keyword), but only use it in type positions -import { Value, type ValueData } from "./provider"; - -// Value is ONLY used in type positions: -export interface Record { - getValue(): Value; // return type - setValue(value: Value): void; // parameter type - readonly currentValue: Value; // type annotation -} - -export function processRecord( - value: Value, // parameter type - callback: (result: Value) => void, // parameter type in callback -): Value { - // return type - callback(value); - return value; -} - -export class BaseProcessor { - current: Value | null = null; -} - - -//// [provider.js] -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.Value = void 0; -class Value { - data = ""; -} -exports.Value = Value; -//// [consumer.js] -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.BaseProcessor = void 0; -exports.processRecord = processRecord; -function processRecord(value, // parameter type -callback) { - // return type - callback(value); - return value; -} -class BaseProcessor { - current = null; -} -exports.BaseProcessor = BaseProcessor; diff --git a/testdata/baselines/reference/compiler/importValueOnlyUsedAsType.symbols b/testdata/baselines/reference/compiler/importValueOnlyUsedAsType.symbols deleted file mode 100644 index 8e817f9971d..00000000000 --- a/testdata/baselines/reference/compiler/importValueOnlyUsedAsType.symbols +++ /dev/null @@ -1,69 +0,0 @@ -//// [tests/cases/compiler/importValueOnlyUsedAsType.ts] //// - -=== provider.ts === -export class Value { ->Value : Symbol(Value, Decl(provider.ts, 0, 0)) - - data: string = ""; ->data : Symbol(Value.data, Decl(provider.ts, 0, 20)) -} -export type ValueData = { data: string }; ->ValueData : Symbol(ValueData, Decl(provider.ts, 2, 1)) ->data : Symbol(data, Decl(provider.ts, 3, 25)) - -=== consumer.ts === -// Import Value as a value (no `type` keyword), but only use it in type positions -import { Value, type ValueData } from "./provider"; ->Value : Symbol(Value, Decl(consumer.ts, 1, 8)) ->ValueData : Symbol(ValueData, Decl(consumer.ts, 1, 15)) - -// Value is ONLY used in type positions: -export interface Record { ->Record : Symbol(Record, Decl(consumer.ts, 1, 51)) - - getValue(): Value; // return type ->getValue : Symbol(Record.getValue, Decl(consumer.ts, 4, 25)) ->Value : Symbol(Value, Decl(consumer.ts, 1, 8)) - - setValue(value: Value): void; // parameter type ->setValue : Symbol(Record.setValue, Decl(consumer.ts, 5, 22)) ->value : Symbol(value, Decl(consumer.ts, 6, 13)) ->Value : Symbol(Value, Decl(consumer.ts, 1, 8)) - - readonly currentValue: Value; // type annotation ->currentValue : Symbol(Record.currentValue, Decl(consumer.ts, 6, 33)) ->Value : Symbol(Value, Decl(consumer.ts, 1, 8)) -} - -export function processRecord( ->processRecord : Symbol(processRecord, Decl(consumer.ts, 8, 1)) - - value: Value, // parameter type ->value : Symbol(value, Decl(consumer.ts, 10, 30)) ->Value : Symbol(Value, Decl(consumer.ts, 1, 8)) - - callback: (result: Value) => void, // parameter type in callback ->callback : Symbol(callback, Decl(consumer.ts, 11, 17)) ->result : Symbol(result, Decl(consumer.ts, 12, 15)) ->Value : Symbol(Value, Decl(consumer.ts, 1, 8)) - -): Value { ->Value : Symbol(Value, Decl(consumer.ts, 1, 8)) - - // return type - callback(value); ->callback : Symbol(callback, Decl(consumer.ts, 11, 17)) ->value : Symbol(value, Decl(consumer.ts, 10, 30)) - - return value; ->value : Symbol(value, Decl(consumer.ts, 10, 30)) -} - -export class BaseProcessor { ->BaseProcessor : Symbol(BaseProcessor, Decl(consumer.ts, 17, 1)) - - current: Value | null = null; ->current : Symbol(BaseProcessor.current, Decl(consumer.ts, 19, 28)) ->Value : Symbol(Value, Decl(consumer.ts, 1, 8)) -} - diff --git a/testdata/baselines/reference/compiler/importValueOnlyUsedAsType.types b/testdata/baselines/reference/compiler/importValueOnlyUsedAsType.types deleted file mode 100644 index fa94ce92e09..00000000000 --- a/testdata/baselines/reference/compiler/importValueOnlyUsedAsType.types +++ /dev/null @@ -1,61 +0,0 @@ -//// [tests/cases/compiler/importValueOnlyUsedAsType.ts] //// - -=== provider.ts === -export class Value { ->Value : Value - - data: string = ""; ->data : string ->"" : "" -} -export type ValueData = { data: string }; ->ValueData : ValueData ->data : string - -=== consumer.ts === -// Import Value as a value (no `type` keyword), but only use it in type positions -import { Value, type ValueData } from "./provider"; ->Value : typeof Value ->ValueData : any - -// Value is ONLY used in type positions: -export interface Record { - getValue(): Value; // return type ->getValue : () => Value - - setValue(value: Value): void; // parameter type ->setValue : (value: Value) => void ->value : Value - - readonly currentValue: Value; // type annotation ->currentValue : Value -} - -export function processRecord( ->processRecord : (value: Value, callback: (result: Value) => void) => Value - - value: Value, // parameter type ->value : Value - - callback: (result: Value) => void, // parameter type in callback ->callback : (result: Value) => void ->result : Value - -): Value { - // return type - callback(value); ->callback(value) : void ->callback : (result: Value) => void ->value : Value - - return value; ->value : Value -} - -export class BaseProcessor { ->BaseProcessor : BaseProcessor - - current: Value | null = null; ->current : Value -} - diff --git a/testdata/tests/cases/compiler/importValueOnlyUsedAsType.ts b/testdata/tests/cases/compiler/importValueOnlyUsedAsType.ts deleted file mode 100644 index 731ea852e31..00000000000 --- a/testdata/tests/cases/compiler/importValueOnlyUsedAsType.ts +++ /dev/null @@ -1,32 +0,0 @@ -// @module: commonjs -// @target: es2020 - -// @filename: provider.ts -export class Value { - data: string = ""; -} -export type ValueData = { data: string }; - -// @filename: consumer.ts -// Import Value as a value (no `type` keyword), but only use it in type positions -import { Value, type ValueData } from "./provider"; - -// Value is ONLY used in type positions: -export interface Record { - getValue(): Value; // return type - setValue(value: Value): void; // parameter type - readonly currentValue: Value; // type annotation -} - -export function processRecord( - value: Value, // parameter type - callback: (result: Value) => void, // parameter type in callback -): Value { - // return type - callback(value); - return value; -} - -export class BaseProcessor { - current: Value | null = null; -} From 400d9cda9ae9fa0b598fd6e2de4e788ab2a5e82e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Feb 2026 19:47:31 +0000 Subject: [PATCH 10/11] Simplify import elision logic by removing redundant isElisionBlocked check The conditional checks for isElisionBlocked and importClause comparison were unnecessary since we always want to return the UpdateImportDeclaration result after the importClause nil check. Co-authored-by: jakebailey <5341706+jakebailey@users.noreply.github.com> --- internal/transformers/tstransforms/importelision.go | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/internal/transformers/tstransforms/importelision.go b/internal/transformers/tstransforms/importelision.go index 3ff144138ab..3dec53d487e 100644 --- a/internal/transformers/tstransforms/importelision.go +++ b/internal/transformers/tstransforms/importelision.go @@ -49,13 +49,7 @@ func (tx *ImportElisionTransformer) visit(node *ast.Node) *ast.Node { // All import bindings were elided, so elide the import declaration return nil } - if !tx.isElisionBlocked(node) { - return tx.Factory().UpdateImportDeclaration(n, n.Modifiers(), importClause, n.ModuleSpecifier, tx.Visitor().VisitNode(n.Attributes)) - } - // If elision is blocked but importClause changed, we still need to update - if importClause != n.ImportClause { - return tx.Factory().UpdateImportDeclaration(n, n.Modifiers(), importClause, n.ModuleSpecifier, tx.Visitor().VisitNode(n.Attributes)) - } + return tx.Factory().UpdateImportDeclaration(n, n.Modifiers(), importClause, n.ModuleSpecifier, tx.Visitor().VisitNode(n.Attributes)) } return tx.Visitor().VisitEachChild(node) case ast.KindImportClause: From df2e210fc08162fa09e8fb643ec10a073e7ef267 Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Tue, 3 Feb 2026 11:59:45 -0800 Subject: [PATCH 11/11] Apply suggestion from @jakebailey --- internal/transformers/tstransforms/importelision.go | 1 - 1 file changed, 1 deletion(-) diff --git a/internal/transformers/tstransforms/importelision.go b/internal/transformers/tstransforms/importelision.go index 3dec53d487e..e31d009ad06 100644 --- a/internal/transformers/tstransforms/importelision.go +++ b/internal/transformers/tstransforms/importelision.go @@ -46,7 +46,6 @@ func (tx *ImportElisionTransformer) visit(node *ast.Node) *ast.Node { if n.ImportClause != nil { importClause := tx.Visitor().VisitNode(n.ImportClause) if importClause == nil { - // All import bindings were elided, so elide the import declaration return nil } return tx.Factory().UpdateImportDeclaration(n, n.Modifiers(), importClause, n.ModuleSpecifier, tx.Visitor().VisitNode(n.Attributes))