Regression from typescript@6.0.2 to typescript@7.0.2. Also reproduces on @typescript/native-preview@7.0.0-dev.20260707.2 and on current main. node v24.13.0, darwin/arm64.
Steps to reproduce
Three files, no tsconfig.json:
lib.ts
require.js
// @ts-check
const { value } = require('./lib.ts');
console.log(value);
side-effect.ts
tsc --noEmit --allowJs require.js side-effect.ts
Behavior with typescript@6.0.2
require.js(2,27): error TS5097: An import path can only end with a '.ts' extension when 'allowImportingTsExtensions' is enabled.
side-effect.ts(1,8): error TS5097: An import path can only end with a '.ts' extension when 'allowImportingTsExtensions' is enabled.
Behavior with typescript@7.0.2
No output — both are silently accepted.
This is not a module-resolution difference: 7.0 resolves require('./lib.ts') and type-checks against it. Changing console.log(value) to value(123) yields the same TS2349 from both versions at the same position — only TS5097 is missing.
Root cause
The two versions guard the diagnostic differently:
7.0 — tsc/internal/checker/checker.go:15334 if ast.FindAncestor(location, ast.IsEmittableImport) != nil {
6.0 — src/compiler/checker.ts:4779 if (errorNode && !(importOrExport?.isTypeOnly || findAncestor(location, isImportTypeNode))) {
7.0 asks "will this import survive emit?"; 6.0 asks "is this explicitly type-only?". The answers differ for exactly two shapes: a CommonJS require(), which is a plain CallExpression that IsEmittableImport never matched, and a clause-less side-effect import, which stopped matching in typescript-go#1198 — a fix for typescript-go#1190 in the adjacent .d.ts branch, which shares the same predicate.
Full matrix
| case |
6.0.2 |
7.0.2 |
require('./lib.ts') in checked JS |
TS5097 |
— |
import './lib.ts' |
TS5097 |
— |
import { value } from './lib.ts' |
TS5097 |
TS5097 |
import { value } from './lib.ts' in JS |
TS5097 |
TS5097 |
export { value } from './lib.ts' |
TS5097 |
TS5097 |
import('./lib.ts') |
TS5097 |
TS5097 |
import lib = require('./lib.ts') |
TS5097 |
TS5097 |
import type { Thing } from './lib.ts' |
— |
— |
export type { Thing } from './lib.ts' |
— |
— |
import('./lib.ts').Thing in type position |
— |
— |
import './types.d.ts' |
— |
— |
The versions disagree only on the first two rows; the last four are negative cases where both correctly stay silent.
Regression from
typescript@6.0.2totypescript@7.0.2. Also reproduces on@typescript/native-preview@7.0.0-dev.20260707.2and on currentmain. node v24.13.0, darwin/arm64.Steps to reproduce
Three files, no
tsconfig.json:lib.tsrequire.jsside-effect.tsBehavior with
typescript@6.0.2Behavior with
typescript@7.0.2No output — both are silently accepted.
This is not a module-resolution difference: 7.0 resolves
require('./lib.ts')and type-checks against it. Changingconsole.log(value)tovalue(123)yields the same TS2349 from both versions at the same position — only TS5097 is missing.Root cause
The two versions guard the diagnostic differently:
7.0 asks "will this import survive emit?"; 6.0 asks "is this explicitly type-only?". The answers differ for exactly two shapes: a CommonJS
require(), which is a plainCallExpressionthatIsEmittableImportnever matched, and a clause-less side-effect import, which stopped matching in typescript-go#1198 — a fix for typescript-go#1190 in the adjacent.d.tsbranch, which shares the same predicate.Full matrix
require('./lib.ts')in checked JSimport './lib.ts'import { value } from './lib.ts'import { value } from './lib.ts'in JSexport { value } from './lib.ts'import('./lib.ts')import lib = require('./lib.ts')import type { Thing } from './lib.ts'export type { Thing } from './lib.ts'import('./lib.ts').Thingin type positionimport './types.d.ts'The versions disagree only on the first two rows; the last four are negative cases where both correctly stay silent.