microsoft / microsoft/TypeScript

TS5097 is not reported for `require()` in checked JS or for clause-less side-effect imports

Open
#64,028 0 comments 1 reaction 3 assignees Claimed by @RyanCavanaugh View on GitHub
Bug
Dominant language
Go
Stars
111k
Forks
14.3k
Avg merge
2d 4h
Merged PRs (30d)
132

Description

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`

```ts
export const value = 1;
```

`require.js`

```js
// @ts-check
const { value } = require('./lib.ts');
console.log(value);
```

`side-effect.ts`

```ts
import './lib.ts';
```

```sh
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`](https://github.com/microsoft/TypeScript/blob/main/tsc/internal/ast/utilities.go#L3205) never matched, and a clause-less side-effect import, which stopped matching in [typescript-go#1198](https://github.com/microsoft/typescript-go/pull/1198) — a fix for [typescript-go#1190](https://github.com/microsoft/typescript-go/issues/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.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.