🔎 Search Terms
normalizeSlashes, getLocalModuleSpecifier, computeModuleSpecifiers, getSpecifierForModuleSymbol, symbolToNode, "Cannot read properties of undefined (reading 'includes')"
🕗 Version & Regression Information
Crashes on typescript@6.0.3. The identical project on typescript@5.9.3 does not crash — nothing else changed, only the typescript dependency. So this is a regression between 5.9 and 6.0.
⏯ Playground Link
Not reproducible in the playground: it needs a Program created from a tsconfig with a package in node_modules, and a second parse of modified file text.
💻 Code
Four files. npm install, then two commands.
package.json
{
"name": "ts6-fix-crash-repro",
"private": true,
"type": "module",
"devDependencies": {
"eslint": "10.10.0",
"typescript": "6.0.3",
"typescript-eslint": "8.70.1",
"vitest": "5.0.0"
}
}
tsconfig.json
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"strict": true,
"noEmit": true
},
"include": ["src"]
}
eslint.config.mjs
import tseslint from 'typescript-eslint';
export default tseslint.config({
files: ['src/**/*.ts'],
extends: [...tseslint.configs.recommendedTypeChecked],
languageOptions: {
parserOptions: { project: './tsconfig.json', tsconfigRootDir: import.meta.dirname },
},
});
src/index.ts
/* eslint-disable @typescript-eslint/no-explicit-any */
import { vi } from 'vitest';
export const f = vi.fn();
Run:
npx eslint src # exit 0 — one warning, the unused disable directive
npx eslint src --fix-dry-run # exit 2 — crash
The eslint-disable comment on line 1 is deliberately unused. It is the only fixable thing in the project, so it is what makes the second command apply a fix and re-parse the file. Delete that line and --fix-dry-run exits cleanly.
🙁 Actual behavior
TypeError: Cannot read properties of undefined (reading 'includes')
Occurred while linting .../src/index.ts:4
Rule: "@typescript-eslint/no-unsafe-assignment"
at normalizeSlashes (typescript/lib/typescript.js:8853:15)
at getNormalizedAbsolutePath (typescript/lib/typescript.js:8899:12)
at getLocalModuleSpecifier (typescript/lib/typescript.js:50210:25)
at computeModuleSpecifiers (typescript/lib/typescript.js:50155:19)
at getModuleSpecifiersWithCacheInfo (typescript/lib/typescript.js:50089:18)
at getModuleSpecifiers (typescript/lib/typescript.js:50056:10)
at getSpecifierForModuleSymbol (typescript/lib/typescript.js:57728:27)
at createExpressionFromSymbolChain (typescript/lib/typescript.js:57987:29)
at symbolToExpression (typescript/lib/typescript.js:57974:14)
at symbolToNode (typescript/lib/typescript.js:55769:14)
normalizeSlashes is called with undefined. The named rule is incidental — it is whichever type-aware rule reaches the file first. Disabling no-unsafe-assignment moves the crash to no-misused-promises, and disabling that moves it to no-unnecessary-type-assertion.
🙂 Expected behavior
getLocalModuleSpecifier should not dereference an absent path on the importing file, or the caller should not reach it with one. Formatting a type should not throw.
Additional information
Conditions, each verified by changing one thing and re-running:
| change |
result |
typescript@6.0.3 |
crash |
typescript@5.9.3, everything else identical |
no crash |
parserOptions.project |
crash |
parserOptions.projectService: true instead |
no crash |
TSESTREE_SINGLE_RUN=false in the environment |
no crash |
remove the unused eslint-disable line (so no fix is applied) |
no crash |
recommendedTypeChecked or strictTypeChecked |
crash either way |
npm or pnpm for the install |
crash either way |
A fix being applied is necessary: without one there is no second parse and no crash.
I could not reduce this below the ESLint harness. Calling checker.typeToString(type, enclosingDeclaration, TypeFormatFlags.UseFullyQualifiedType) directly does not reproduce it, including when enclosingDeclaration is a SourceFile from a different Program, or one built with ts.createSourceFile whose path I removed — those all format fine. So the pathless importing file appears to arise from something more specific than a detached SourceFile, and I have not identified what.
Related: typescript-eslint#12749 reported the same stack from a different trigger (CI=true, so the same single-run Program inference that TSESTREE_SINGLE_RUN=false disables above). It was closed as external on the grounds that the stack is inside TypeScript, with a request to file here and to include a reproduction. This is that reproduction.
Environment: macOS 27.0 arm64, Node v26.8.1.
🔎 Search Terms
normalizeSlashes, getLocalModuleSpecifier, computeModuleSpecifiers, getSpecifierForModuleSymbol, symbolToNode, "Cannot read properties of undefined (reading 'includes')"
🕗 Version & Regression Information
Crashes on typescript@6.0.3. The identical project on typescript@5.9.3 does not crash — nothing else changed, only the
typescriptdependency. So this is a regression between 5.9 and 6.0.⏯ Playground Link
Not reproducible in the playground: it needs a
Programcreated from a tsconfig with a package innode_modules, and a second parse of modified file text.💻 Code
Four files.
npm install, then two commands.package.json{ "name": "ts6-fix-crash-repro", "private": true, "type": "module", "devDependencies": { "eslint": "10.10.0", "typescript": "6.0.3", "typescript-eslint": "8.70.1", "vitest": "5.0.0" } }tsconfig.json{ "compilerOptions": { "target": "ES2022", "module": "NodeNext", "moduleResolution": "NodeNext", "strict": true, "noEmit": true }, "include": ["src"] }eslint.config.mjssrc/index.tsRun:
The
eslint-disablecomment on line 1 is deliberately unused. It is the only fixable thing in the project, so it is what makes the second command apply a fix and re-parse the file. Delete that line and--fix-dry-runexits cleanly.🙁 Actual behavior
normalizeSlashesis called withundefined. The named rule is incidental — it is whichever type-aware rule reaches the file first. Disablingno-unsafe-assignmentmoves the crash tono-misused-promises, and disabling that moves it tono-unnecessary-type-assertion.🙂 Expected behavior
getLocalModuleSpecifiershould not dereference an absent path on the importing file, or the caller should not reach it with one. Formatting a type should not throw.Additional information
Conditions, each verified by changing one thing and re-running:
typescript@6.0.3typescript@5.9.3, everything else identicalparserOptions.projectparserOptions.projectService: trueinsteadTSESTREE_SINGLE_RUN=falsein the environmenteslint-disableline (so no fix is applied)recommendedTypeCheckedorstrictTypeCheckednpmorpnpmfor the installA fix being applied is necessary: without one there is no second parse and no crash.
I could not reduce this below the ESLint harness. Calling
checker.typeToString(type, enclosingDeclaration, TypeFormatFlags.UseFullyQualifiedType)directly does not reproduce it, including whenenclosingDeclarationis aSourceFilefrom a differentProgram, or one built withts.createSourceFilewhosepathI removed — those all format fine. So the pathless importing file appears to arise from something more specific than a detachedSourceFile, and I have not identified what.Related: typescript-eslint#12749 reported the same stack from a different trigger (
CI=true, so the same single-runPrograminference thatTSESTREE_SINGLE_RUN=falsedisables above). It was closed as external on the grounds that the stack is inside TypeScript, with a request to file here and to include a reproduction. This is that reproduction.Environment: macOS 27.0 arm64, Node v26.8.1.