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:: +a.js:1:7 - error TS8010: Type annotations can only be used in TypeScript files. + +1 var R: ; +   ~ + +a.js:1:8 - error TS1110: Type expected. + +1 var R: ; +   ~ + + +Found 2 errors in the same file, starting at: a.js:1 + +//// [/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:: +a.js:1:11 - error TS8010: Type annotations can only be used in TypeScript files. + +1 var R: number ; +   ~~~~~~ + + +Found 1 error in a.js:1 + +//// [/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; }; +