diff --git a/tsc/internal/execute/tsctests/tsc_test.go b/tsc/internal/execute/tsctests/tsc_test.go
index ee429a826f94a..681425333a8f2 100644
--- a/tsc/internal/execute/tsctests/tsc_test.go
+++ b/tsc/internal/execute/tsctests/tsc_test.go
@@ -55,6 +55,28 @@ func TestOutputSanitizerVersionPadding(t *testing.T) {
}
}
+// https://github.com/microsoft/TypeScript/issues/65078
+func TestTscJSDiagnosticsForMissingNodes(t *testing.T) {
+ t.Parallel()
+ for _, test := range []struct {
+ name string
+ source string
+ }{
+ {name: "missing type annotation", source: "var R: ;"},
+ {name: "present type annotation with leading trivia", source: "var R: number ;"},
+ } {
+ for _, pretty := range []string{"true", "false"} {
+ (&tscInput{
+ subScenario: test.name + " pretty " + pretty,
+ files: FileMap{
+ "/home/src/workspaces/project/a.js": test.source,
+ },
+ commandLineArgs: []string{"a.js", "--allowJs", "--noEmit", "--pretty", pretty},
+ }).run(t, "jsDiagnosticsForMissingNodes")
+ }
+ }
+}
+
func TestTscCommandline(t *testing.T) {
t.Parallel()
colorTest := func(subScenario string, env map[string]string, outputIsTTY bool) *tscInput {
diff --git a/tsc/internal/parser/parser.go b/tsc/internal/parser/parser.go
index db1bb03defb8c..4cd2aac15dc0e 100644
--- a/tsc/internal/parser/parser.go
+++ b/tsc/internal/parser/parser.go
@@ -6825,6 +6825,13 @@ func (p *Parser) parseResolutionMode(mode string, pos int, end int) (resolutionK
return resolutionKind
}
+func (p *Parser) jsErrorAtNode(node *ast.Node, message *diagnostics.Message, args ...any) {
+ // getErrorSpanForNode does not skip trivia for missing nodes, so a diagnostic
+ // anchored at an empty location reports the position where the node would have
+ // been instead of the following token (see https://github.com/microsoft/TypeScript/issues/65078).
+ p.jsDiagnostics = append(p.jsDiagnostics, ast.NewDiagnostic(nil, getErrorSpanForNode(p.sourceText, node), message, args...))
+}
+
func (p *Parser) jsErrorAtRange(loc core.TextRange, message *diagnostics.Message, args ...any) {
p.jsDiagnostics = append(p.jsDiagnostics, ast.NewDiagnostic(nil, core.NewTextRange(scanner.SkipTrivia(p.sourceText, loc.Pos()), loc.End()), message, args...))
}
@@ -6838,7 +6845,7 @@ func (p *Parser) checkJSDecoratorSyntax(node *ast.Node) {
if ast.CanHaveIllegalDecorators(node) {
for _, modifier := range modifiers {
if ast.IsDecorator(modifier) {
- p.jsErrorAtRange(modifier.Loc, diagnostics.Decorators_are_not_valid_here)
+ p.jsErrorAtNode(modifier, diagnostics.Decorators_are_not_valid_here)
break
}
}
@@ -6853,7 +6860,7 @@ func (p *Parser) checkJSDecoratorSyntax(node *ast.Node) {
})
if decoratorIndex > exportIndex && defaultIndex >= 0 && decoratorIndex < defaultIndex {
// Decorator between `export` and `default`
- p.jsErrorAtRange(modifiers[decoratorIndex].Loc, diagnostics.Decorators_are_not_valid_here)
+ p.jsErrorAtNode(modifiers[decoratorIndex], diagnostics.Decorators_are_not_valid_here)
} else if decoratorIndex < exportIndex {
// Find a trailing decorator after the export keyword
trailingDecoratorIndex := -1
@@ -6890,56 +6897,56 @@ func (p *Parser) checkJSSyntax(node *ast.Node) *ast.Node {
switch node.Kind {
case ast.KindParameter, ast.KindPropertyDeclaration, ast.KindMethodDeclaration:
if token := node.QuestionToken(); token != nil && token.Flags&ast.NodeFlagsReparsed == 0 && ast.IsQuestionToken(token) {
- p.jsErrorAtRange(token.Loc, diagnostics.The_0_modifier_can_only_be_used_in_TypeScript_files, "?")
+ p.jsErrorAtNode(token, diagnostics.The_0_modifier_can_only_be_used_in_TypeScript_files, "?")
}
fallthrough
case ast.KindMethodSignature, ast.KindConstructor, ast.KindGetAccessor, ast.KindSetAccessor, ast.KindFunctionExpression,
ast.KindFunctionDeclaration, ast.KindArrowFunction, ast.KindVariableDeclaration, ast.KindIndexSignature:
if ast.IsFunctionLike(node) && node.Body() == nil {
- p.jsErrorAtRange(node.Loc, diagnostics.Signature_declarations_can_only_be_used_in_TypeScript_files)
+ p.jsErrorAtNode(node, diagnostics.Signature_declarations_can_only_be_used_in_TypeScript_files)
} else if t := node.Type(); t != nil && t.Flags&ast.NodeFlagsReparsed == 0 {
- p.jsErrorAtRange(t.Loc, diagnostics.Type_annotations_can_only_be_used_in_TypeScript_files)
+ p.jsErrorAtNode(t, diagnostics.Type_annotations_can_only_be_used_in_TypeScript_files)
}
case ast.KindImportDeclaration:
if clause := node.ImportClause(); clause != nil && clause.IsTypeOnly() {
- p.jsErrorAtRange(node.Loc, diagnostics.X_0_declarations_can_only_be_used_in_TypeScript_files, "import type")
+ p.jsErrorAtNode(node, diagnostics.X_0_declarations_can_only_be_used_in_TypeScript_files, "import type")
}
case ast.KindExportDeclaration:
if node.IsTypeOnly() {
- p.jsErrorAtRange(node.Loc, diagnostics.X_0_declarations_can_only_be_used_in_TypeScript_files, "export type")
+ p.jsErrorAtNode(node, diagnostics.X_0_declarations_can_only_be_used_in_TypeScript_files, "export type")
}
case ast.KindImportSpecifier:
if node.IsTypeOnly() {
- p.jsErrorAtRange(node.Loc, diagnostics.X_0_declarations_can_only_be_used_in_TypeScript_files, "import...type")
+ p.jsErrorAtNode(node, diagnostics.X_0_declarations_can_only_be_used_in_TypeScript_files, "import...type")
}
case ast.KindExportSpecifier:
if node.IsTypeOnly() {
- p.jsErrorAtRange(node.Loc, diagnostics.X_0_declarations_can_only_be_used_in_TypeScript_files, "export...type")
+ p.jsErrorAtNode(node, diagnostics.X_0_declarations_can_only_be_used_in_TypeScript_files, "export...type")
}
case ast.KindImportEqualsDeclaration:
- p.jsErrorAtRange(node.Loc, diagnostics.X_import_can_only_be_used_in_TypeScript_files)
+ p.jsErrorAtNode(node, diagnostics.X_import_can_only_be_used_in_TypeScript_files)
case ast.KindExportAssignment:
if node.AsExportAssignment().IsExportEquals {
- p.jsErrorAtRange(node.Loc, diagnostics.X_export_can_only_be_used_in_TypeScript_files)
+ p.jsErrorAtNode(node, diagnostics.X_export_can_only_be_used_in_TypeScript_files)
}
case ast.KindHeritageClause:
if node.AsHeritageClause().Token == ast.KindImplementsKeyword {
- p.jsErrorAtRange(node.Loc, diagnostics.X_implements_clauses_can_only_be_used_in_TypeScript_files)
+ p.jsErrorAtNode(node, diagnostics.X_implements_clauses_can_only_be_used_in_TypeScript_files)
}
case ast.KindInterfaceDeclaration:
- p.jsErrorAtRange(node.Name().Loc, diagnostics.X_0_declarations_can_only_be_used_in_TypeScript_files, "interface")
+ p.jsErrorAtNode(node.Name(), diagnostics.X_0_declarations_can_only_be_used_in_TypeScript_files, "interface")
case ast.KindModuleDeclaration:
- p.jsErrorAtRange(node.Name().Loc, diagnostics.X_0_declarations_can_only_be_used_in_TypeScript_files, scanner.TokenToString(node.AsModuleDeclaration().Keyword))
+ p.jsErrorAtNode(node.Name(), diagnostics.X_0_declarations_can_only_be_used_in_TypeScript_files, scanner.TokenToString(node.AsModuleDeclaration().Keyword))
case ast.KindTypeAliasDeclaration:
- p.jsErrorAtRange(node.Name().Loc, diagnostics.Type_aliases_can_only_be_used_in_TypeScript_files)
+ p.jsErrorAtNode(node.Name(), diagnostics.Type_aliases_can_only_be_used_in_TypeScript_files)
case ast.KindEnumDeclaration:
- p.jsErrorAtRange(node.Name().Loc, diagnostics.X_0_declarations_can_only_be_used_in_TypeScript_files, "enum")
+ p.jsErrorAtNode(node.Name(), diagnostics.X_0_declarations_can_only_be_used_in_TypeScript_files, "enum")
case ast.KindNonNullExpression:
- p.jsErrorAtRange(node.Loc, diagnostics.Non_null_assertions_can_only_be_used_in_TypeScript_files)
+ p.jsErrorAtNode(node, diagnostics.Non_null_assertions_can_only_be_used_in_TypeScript_files)
case ast.KindAsExpression:
- p.jsErrorAtRange(node.Type().Loc, diagnostics.Type_assertion_expressions_can_only_be_used_in_TypeScript_files)
+ p.jsErrorAtNode(node.Type(), diagnostics.Type_assertion_expressions_can_only_be_used_in_TypeScript_files)
case ast.KindSatisfiesExpression:
- p.jsErrorAtRange(node.Type().Loc, diagnostics.Type_satisfaction_expressions_can_only_be_used_in_TypeScript_files)
+ p.jsErrorAtNode(node.Type(), diagnostics.Type_satisfaction_expressions_can_only_be_used_in_TypeScript_files)
}
// Check decorator placement in JS files
p.checkJSDecoratorSyntax(node)
@@ -6954,7 +6961,7 @@ func (p *Parser) checkJSSyntax(node *ast.Node) *ast.Node {
case ast.KindVariableStatement, ast.KindPropertyDeclaration:
for _, modifier := range node.ModifierNodes() {
if modifier.Flags&ast.NodeFlagsReparsed == 0 && modifier.Kind != ast.KindDecorator && ast.ModifierToFlag(modifier.Kind)&ast.ModifierFlagsJavaScript == 0 {
- p.jsErrorAtRange(modifier.Loc, diagnostics.The_0_modifier_can_only_be_used_in_TypeScript_files, scanner.TokenToString(modifier.Kind))
+ p.jsErrorAtNode(modifier, diagnostics.The_0_modifier_can_only_be_used_in_TypeScript_files, scanner.TokenToString(modifier.Kind))
}
}
case ast.KindParameter:
diff --git a/tsc/testdata/baselines/reference/tsc/jsDiagnosticsForMissingNodes/missing-type-annotation-pretty-false.js b/tsc/testdata/baselines/reference/tsc/jsDiagnosticsForMissingNodes/missing-type-annotation-pretty-false.js
new file mode 100644
index 0000000000000..a19a12b1f3cea
--- /dev/null
+++ b/tsc/testdata/baselines/reference/tsc/jsDiagnosticsForMissingNodes/missing-type-annotation-pretty-false.js
@@ -0,0 +1,35 @@
+currentDirectory::/home/src/workspaces/project
+useCaseSensitiveFileNames::true
+Input::
+//// [/home/src/workspaces/project/a.js] *new*
+var R: ;
+
+tsgo a.js --allowJs --noEmit --pretty false
+ExitStatus:: DiagnosticsPresent_OutputsGenerated
+Output::
+a.js(1,7): error TS8010: Type annotations can only be used in TypeScript files.
+a.js(1,8): error TS1110: Type expected.
+//// [/home/src/tslibs/TS/Lib/lib.es2026.full.d.ts] *Lib*
+///
+interface Boolean {}
+interface Function {}
+interface CallableFunction {}
+interface NewableFunction {}
+interface IArguments {}
+interface Number { toExponential: any; }
+interface Object {}
+interface RegExp {}
+interface String { charAt: any; }
+interface Array { length: number; [n: number]: T; }
+interface ReadonlyArray {}
+interface SymbolConstructor {
+ (desc?: string | number): symbol;
+ for(name: string): symbol;
+ readonly toStringTag: symbol;
+}
+declare var Symbol: SymbolConstructor;
+interface Symbol {
+ readonly [Symbol.toStringTag]: string;
+}
+declare const console: { log(msg: any): void; };
+
diff --git a/tsc/testdata/baselines/reference/tsc/jsDiagnosticsForMissingNodes/missing-type-annotation-pretty-true.js b/tsc/testdata/baselines/reference/tsc/jsDiagnosticsForMissingNodes/missing-type-annotation-pretty-true.js
new file mode 100644
index 0000000000000..682cef8657acf
--- /dev/null
+++ b/tsc/testdata/baselines/reference/tsc/jsDiagnosticsForMissingNodes/missing-type-annotation-pretty-true.js
@@ -0,0 +1,46 @@
+currentDirectory::/home/src/workspaces/project
+useCaseSensitiveFileNames::true
+Input::
+//// [/home/src/workspaces/project/a.js] *new*
+var R: ;
+
+tsgo a.js --allowJs --noEmit --pretty true
+ExitStatus:: DiagnosticsPresent_OutputsGenerated
+Output::
+[96ma.js[0m:[93m1[0m:[93m7[0m - [91merror[0m[90m TS8010: [0mType annotations can only be used in TypeScript files.
+
+[7m1[0m var R: ;
+[7m [0m [91m ~[0m
+
+[96ma.js[0m:[93m1[0m:[93m8[0m - [91merror[0m[90m TS1110: [0mType expected.
+
+[7m1[0m var R: ;
+[7m [0m [91m ~[0m
+
+
+Found 2 errors in the same file, starting at: a.js[90m:1[0m
+
+//// [/home/src/tslibs/TS/Lib/lib.es2026.full.d.ts] *Lib*
+///
+interface Boolean {}
+interface Function {}
+interface CallableFunction {}
+interface NewableFunction {}
+interface IArguments {}
+interface Number { toExponential: any; }
+interface Object {}
+interface RegExp {}
+interface String { charAt: any; }
+interface Array { length: number; [n: number]: T; }
+interface ReadonlyArray {}
+interface SymbolConstructor {
+ (desc?: string | number): symbol;
+ for(name: string): symbol;
+ readonly toStringTag: symbol;
+}
+declare var Symbol: SymbolConstructor;
+interface Symbol {
+ readonly [Symbol.toStringTag]: string;
+}
+declare const console: { log(msg: any): void; };
+
diff --git a/tsc/testdata/baselines/reference/tsc/jsDiagnosticsForMissingNodes/present-type-annotation-with-leading-trivia-pretty-false.js b/tsc/testdata/baselines/reference/tsc/jsDiagnosticsForMissingNodes/present-type-annotation-with-leading-trivia-pretty-false.js
new file mode 100644
index 0000000000000..0e6118fc4c2e8
--- /dev/null
+++ b/tsc/testdata/baselines/reference/tsc/jsDiagnosticsForMissingNodes/present-type-annotation-with-leading-trivia-pretty-false.js
@@ -0,0 +1,34 @@
+currentDirectory::/home/src/workspaces/project
+useCaseSensitiveFileNames::true
+Input::
+//// [/home/src/workspaces/project/a.js] *new*
+var R: number ;
+
+tsgo a.js --allowJs --noEmit --pretty false
+ExitStatus:: DiagnosticsPresent_OutputsGenerated
+Output::
+a.js(1,11): error TS8010: Type annotations can only be used in TypeScript files.
+//// [/home/src/tslibs/TS/Lib/lib.es2026.full.d.ts] *Lib*
+///
+interface Boolean {}
+interface Function {}
+interface CallableFunction {}
+interface NewableFunction {}
+interface IArguments {}
+interface Number { toExponential: any; }
+interface Object {}
+interface RegExp {}
+interface String { charAt: any; }
+interface Array { length: number; [n: number]: T; }
+interface ReadonlyArray {}
+interface SymbolConstructor {
+ (desc?: string | number): symbol;
+ for(name: string): symbol;
+ readonly toStringTag: symbol;
+}
+declare var Symbol: SymbolConstructor;
+interface Symbol {
+ readonly [Symbol.toStringTag]: string;
+}
+declare const console: { log(msg: any): void; };
+
diff --git a/tsc/testdata/baselines/reference/tsc/jsDiagnosticsForMissingNodes/present-type-annotation-with-leading-trivia-pretty-true.js b/tsc/testdata/baselines/reference/tsc/jsDiagnosticsForMissingNodes/present-type-annotation-with-leading-trivia-pretty-true.js
new file mode 100644
index 0000000000000..6cc60b8b6c37a
--- /dev/null
+++ b/tsc/testdata/baselines/reference/tsc/jsDiagnosticsForMissingNodes/present-type-annotation-with-leading-trivia-pretty-true.js
@@ -0,0 +1,41 @@
+currentDirectory::/home/src/workspaces/project
+useCaseSensitiveFileNames::true
+Input::
+//// [/home/src/workspaces/project/a.js] *new*
+var R: number ;
+
+tsgo a.js --allowJs --noEmit --pretty true
+ExitStatus:: DiagnosticsPresent_OutputsGenerated
+Output::
+[96ma.js[0m:[93m1[0m:[93m11[0m - [91merror[0m[90m TS8010: [0mType annotations can only be used in TypeScript files.
+
+[7m1[0m var R: number ;
+[7m [0m [91m ~~~~~~[0m
+
+
+Found 1 error in a.js[90m:1[0m
+
+//// [/home/src/tslibs/TS/Lib/lib.es2026.full.d.ts] *Lib*
+///
+interface Boolean {}
+interface Function {}
+interface CallableFunction {}
+interface NewableFunction {}
+interface IArguments {}
+interface Number { toExponential: any; }
+interface Object {}
+interface RegExp {}
+interface String { charAt: any; }
+interface Array { length: number; [n: number]: T; }
+interface ReadonlyArray {}
+interface SymbolConstructor {
+ (desc?: string | number): symbol;
+ for(name: string): symbol;
+ readonly toStringTag: symbol;
+}
+declare var Symbol: SymbolConstructor;
+interface Symbol {
+ readonly [Symbol.toStringTag]: string;
+}
+declare const console: { log(msg: any): void; };
+