Skip to content

Handle extensionless root files gracefully instead of panicking - #4628

Open
UditDewan wants to merge 2 commits into
microsoft:mainfrom
UditDewan:fix/extensionless-root-file-panic
Open

Handle extensionless root files gracefully instead of panicking#4628
UditDewan wants to merge 2 commits into
microsoft:mainfrom
UditDewan:fix/extensionless-root-file-panic

Conversation

@UditDewan

Copy link
Copy Markdown
Contributor

Fixes #4609

Any root file whose name has no extension crashed the compiler with a Go panic (ScriptKind must be specified when parsing source file) when the file exists on disk — from the CLI, from a tsconfig files entry, and through the LSP/API session path. TypeScript 6 rejects the same inputs gracefully with TS6231.

Root cause

Two related gaps:

  1. In the compiler, addRootFileTask already produced the correct TS6231 diagnostic when an extensionless root file failed extension resolution, but still created a parse task for the unresolved path. parseTask.load only rejects unsupported extensions, not missing ones, so when the extensionless file existed it reached the parser with ScriptKindUnknown and hit the parser's assert.

  2. Files that are legitimately included without a recognized extension (inferred projects force allowNonTsExtensions, so any extensionless open file qualifies) derive their script kind from the file name, which yields Unknown and reached the parser via the compiler host, diskFile.Kind(), or overlay creation. TypeScript 6's ensureScriptKind defaults unknown kinds to TS at the parse boundary; tsgo had no equivalent.

Fix

  • Root file tasks whose name fails extension resolution no longer parse the file; the task only carries its processing diagnostic. tsc script now reports TS6231: Could not resolve the path 'script' with the extensions: ... exactly like TypeScript 6.
  • Added core.EnsureScriptKindFromFileName (unknown → TS, mirroring ensureScriptKind in strada) and used it at the three parse boundaries: compilerHost.GetSourceFile, diskFile.Kind(), and LSP overlay creation. The overlay change also means an unrecognized languageId combined with an extensionless file name can no longer reach the assert.

This may also address #4267, whose reported stack matches the project-path variant here.

Tests

  • Two new tsc baseline scenarios (extensionless file in tsconfig exists, extensionless file on command line exists) — both panicked before this change; the baselines now show the TS6231 output matching TypeScript 6.
  • overlayfs unit tests pinning the TS fallback for extensionless disk files and for overlays opened with an unknown language kind.
  • An end-to-end session test opening an extensionless file in an inferred project and checking it is parsed as TS.

Disclosure

Per the contributing guidelines: this patch was authored with AI assistance. I have read and understand the changes, and I will respond to review feedback myself.

An extensionless root file that exists on disk crashed the compiler with
"ScriptKind must be specified when parsing source file". The root file
task produced the TS6231 diagnostic but still parsed the file, and the
file name gave the parser no script kind to work with.

- Root file tasks whose name fails extension resolution no longer parse
  the file; they only carry their processing diagnostic (TS6231), which
  matches TypeScript 6 behavior.
- Files legitimately included without a recognized extension (e.g. via
  allowNonTsExtensions in inferred projects) now default to ScriptKindTS
  at the parse boundaries, mirroring ensureScriptKind in TypeScript 6.
Copilot AI review requested due to automatic review settings July 14, 2026 04:48

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated no new comments.

Comment thread internal/compiler/host.go
if !ok {
return nil
}
return parser.ParseSourceFile(opts, text, core.GetScriptKindFromFileName(opts.FileName))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this the only place we need to do this?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nah, it wasn't. I went through the other callers that derive a script kind from a file name and found one more live crash: getOrParseSourceFile in internal/ls/sourcedefinition.go. A declaration map's sources entries are arbitrary strings, so a .d.ts.map naming an existing extensionless file panicked custom/textDocument/sourceDefinition with the same assert:

panic handling request custom/textDocument/sourceDefinition: ScriptKind must be specified when parsing source file: /lib/helper

Fixed in the follow-up commit, with a fourslash regression test; it now navigates into the mapped file instead of crashing.

The rest of what I looked at:

  • internal/execute/tsc.go (fmtMain) has the same pattern, but it's dead code today since its only caller, the -f case, is commented out. Happy to switch it over too if you want.
  • internal/project/api.go deliberately returns "unsupported file extension" instead of parsing, so I left it alone.
  • The GetScriptKind field on the ATA request in session.go is never read by the installer.
  • harnessutil panics on purpose in the test harness.

`getOrParseSourceFile` in the source-definition resolver parses whatever a
declaration map lists in `sources`, which is an arbitrary string. When that
names an existing file with no recognized extension, the parse hit the same
"ScriptKind must be specified" assert and crashed the request.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Extensionless root file panics the compiler: "ScriptKind must be specified when parsing source file"

3 participants