Handle extensionless root files gracefully instead of panicking - #4628
Open
UditDewan wants to merge 2 commits into
Open
Handle extensionless root files gracefully instead of panicking#4628UditDewan wants to merge 2 commits into
UditDewan wants to merge 2 commits into
Conversation
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.
jakebailey
reviewed
Jul 21, 2026
| if !ok { | ||
| return nil | ||
| } | ||
| return parser.ParseSourceFile(opts, text, core.GetScriptKindFromFileName(opts.FileName)) |
Member
There was a problem hiding this comment.
Is this the only place we need to do this?
Contributor
Author
There was a problem hiding this comment.
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-fcase, is commented out. Happy to switch it over too if you want.internal/project/api.godeliberately returns "unsupported file extension" instead of parsing, so I left it alone.- The
GetScriptKindfield on the ATA request insession.gois never read by the installer. harnessutilpanics 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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 tsconfigfilesentry, and through the LSP/API session path. TypeScript 6 rejects the same inputs gracefully withTS6231.Root cause
Two related gaps:
In the compiler,
addRootFileTaskalready produced the correctTS6231diagnostic when an extensionless root file failed extension resolution, but still created a parse task for the unresolved path.parseTask.loadonly rejects unsupported extensions, not missing ones, so when the extensionless file existed it reached the parser withScriptKindUnknownand hit the parser's assert.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 yieldsUnknownand reached the parser via the compiler host,diskFile.Kind(), or overlay creation. TypeScript 6'sensureScriptKinddefaults unknown kinds to TS at the parse boundary; tsgo had no equivalent.Fix
tsc scriptnow reportsTS6231: Could not resolve the path 'script' with the extensions: ...exactly like TypeScript 6.core.EnsureScriptKindFromFileName(unknown → TS, mirroringensureScriptKindin strada) and used it at the three parse boundaries:compilerHost.GetSourceFile,diskFile.Kind(), and LSP overlay creation. The overlay change also means an unrecognizedlanguageIdcombined 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
extensionless file in tsconfig exists,extensionless file on command line exists) — both panicked before this change; the baselines now show theTS6231output matching TypeScript 6.overlayfsunit tests pinning the TS fallback for extensionless disk files and for overlays opened with an unknown language kind.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.