Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions tsc/internal/execute/tsctests/tsc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
47 changes: 27 additions & 20 deletions tsc/internal/parser/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -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...))
}
Expand All @@ -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
}
}
Expand All @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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:
Expand Down
Original file line number Diff line number Diff line change
@@ -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*
/// <reference no-default-lib="true"/>
interface Boolean {}
interface Function {}
interface CallableFunction {}
interface NewableFunction {}
interface IArguments {}
interface Number { toExponential: any; }
interface Object {}
interface RegExp {}
interface String { charAt: any; }
interface Array<T> { length: number; [n: number]: T; }
interface ReadonlyArray<T> {}
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; };

Original file line number Diff line number Diff line change
@@ -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*
/// <reference no-default-lib="true"/>
interface Boolean {}
interface Function {}
interface CallableFunction {}
interface NewableFunction {}
interface IArguments {}
interface Number { toExponential: any; }
interface Object {}
interface RegExp {}
interface String { charAt: any; }
interface Array<T> { length: number; [n: number]: T; }
interface ReadonlyArray<T> {}
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; };

Original file line number Diff line number Diff line change
@@ -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*
/// <reference no-default-lib="true"/>
interface Boolean {}
interface Function {}
interface CallableFunction {}
interface NewableFunction {}
interface IArguments {}
interface Number { toExponential: any; }
interface Object {}
interface RegExp {}
interface String { charAt: any; }
interface Array<T> { length: number; [n: number]: T; }
interface ReadonlyArray<T> {}
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; };

Original file line number Diff line number Diff line change
@@ -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*
/// <reference no-default-lib="true"/>
interface Boolean {}
interface Function {}
interface CallableFunction {}
interface NewableFunction {}
interface IArguments {}
interface Number { toExponential: any; }
interface Object {}
interface RegExp {}
interface String { charAt: any; }
interface Array<T> { length: number; [n: number]: T; }
interface ReadonlyArray<T> {}
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; };