diff --git a/crates/oxc_parser/src/diagnostics.rs b/crates/oxc_parser/src/diagnostics.rs index e2d4c67d6778d..7613d6d32420a 100644 --- a/crates/oxc_parser/src/diagnostics.rs +++ b/crates/oxc_parser/src/diagnostics.rs @@ -481,6 +481,11 @@ pub fn identifier_expected(span: Span) -> OxcDiagnostic { OxcDiagnostic::error("Identifier expected.").with_label(span) } +#[cold] +pub fn ts_identifier_expected(span: Span) -> OxcDiagnostic { + ts_error("1003", "Identifier expected.").with_label(span) +} + #[cold] pub fn identifier_reserved_word(span: Span, reserved: &str) -> OxcDiagnostic { OxcDiagnostic::error(format!( @@ -571,6 +576,16 @@ pub fn ts_empty_type_argument_list(span: Span) -> OxcDiagnostic { ts_error("1099", "Type argument list cannot be empty.").with_label(span) } +#[cold] +pub fn ts_namespace_missing_name(span: Span) -> OxcDiagnostic { + ts_error("1437", "Namespace must be given a name.").with_label(span) +} + +#[cold] +pub fn ts_module_missing_name(span: Span) -> OxcDiagnostic { + ts_error("1437", "Module must be given a name.").with_label(span) +} + #[cold] pub fn unexpected_super(span: Span) -> OxcDiagnostic { OxcDiagnostic::error("'super' can only be used with function calls or in property accesses") diff --git a/crates/oxc_parser/src/js/expression.rs b/crates/oxc_parser/src/js/expression.rs index 9d7402e0083ad..8fd73e7ee3c9a 100644 --- a/crates/oxc_parser/src/js/expression.rs +++ b/crates/oxc_parser/src/js/expression.rs @@ -1,6 +1,7 @@ use cow_utils::CowUtils; use oxc_allocator::{Box, TakeIn, Vec}; use oxc_ast::ast::*; +use oxc_diagnostics::OxcDiagnostic; #[cfg(feature = "regular_expression")] use oxc_regular_expression::ast::Pattern; use oxc_span::{Atom, GetSpan, Span}; @@ -88,6 +89,27 @@ impl<'a> ParserImpl<'a> { self.ast.binding_identifier(span, name) } + /// `BindingIdentifier` with custom error for unexpected tokens + pub(crate) fn parse_binding_identifier_with_error( + &mut self, + unexpected_error: impl FnOnce(Span) -> OxcDiagnostic, + ) -> BindingIdentifier<'a> { + let cur = self.cur_kind(); + if !cur.is_binding_identifier() { + return if cur.is_reserved_keyword() { + let error = + diagnostics::identifier_reserved_word(self.cur_token().span(), cur.to_str()); + self.fatal_error(error) + } else { + let error = unexpected_error(self.cur_token().span()); + self.fatal_error(error) + }; + } + self.check_identifier(cur, self.ctx); + let (span, name) = self.parse_identifier_kind(Kind::Ident); + self.ast.binding_identifier(span, name) + } + pub(crate) fn parse_label_identifier(&mut self) -> LabelIdentifier<'a> { let kind = self.cur_kind(); if !kind.is_label_identifier(self.ctx.has_yield(), self.ctx.has_await()) { diff --git a/crates/oxc_parser/src/ts/statement.rs b/crates/oxc_parser/src/ts/statement.rs index 2651b68c5f8e9..66f0ef7190028 100644 --- a/crates/oxc_parser/src/ts/statement.rs +++ b/crates/oxc_parser/src/ts/statement.rs @@ -358,7 +358,25 @@ impl<'a> ParserImpl<'a> { kind: TSModuleDeclarationKind, modifiers: &Modifiers<'a>, ) -> Box<'a, TSModuleDeclaration<'a>> { - let id = TSModuleDeclarationName::Identifier(self.parse_binding_identifier()); + // Check if we're at `{` to determine which error to use + // TS1437: "Namespace/Module must be given a name" for `namespace {` + // TS1003: "Identifier expected" for `namespace "string" {` or other non-identifiers + let is_missing_name = self.cur_kind() == Kind::LCurly; + let id = + TSModuleDeclarationName::Identifier(self.parse_binding_identifier_with_error(|span| { + if is_missing_name { + match kind { + TSModuleDeclarationKind::Namespace => { + diagnostics::ts_namespace_missing_name(span) + } + TSModuleDeclarationKind::Module => { + diagnostics::ts_module_missing_name(span) + } + } + } else { + diagnostics::ts_identifier_expected(span) + } + })); let body = if self.eat(Kind::Dot) { let span = self.start_span(); let decl = self.parse_module_or_namespace_declaration(span, kind, &Modifiers::empty()); @@ -610,7 +628,8 @@ impl<'a> ParserImpl<'a> { self.bump_any(); return !self.cur_token().is_on_new_line() && (self.cur_kind().is_binding_identifier() - || self.cur_kind() == Kind::Str); + || self.cur_kind() == Kind::Str + || self.cur_kind() == Kind::LCurly); } Kind::Abstract | Kind::Accessor diff --git a/tasks/coverage/snapshots/parser_misc.snap b/tasks/coverage/snapshots/parser_misc.snap index e5d9e5d5f6406..6f9c82197cf4f 100644 --- a/tasks/coverage/snapshots/parser_misc.snap +++ b/tasks/coverage/snapshots/parser_misc.snap @@ -357,13 +357,13 @@ Negative Passed: 118/118 (100.00%) · ╰── `}` expected ╰──── - × Unexpected token + × TS(1003): Identifier expected. ╭─[misc/fail/oxc-11592-1.ts:1:11] 1 │ namespace "a" {} · ─── ╰──── - × Unexpected token + × TS(1003): Identifier expected. ╭─[misc/fail/oxc-11592-2.ts:1:11] 1 │ namespace "a"; · ─── diff --git a/tasks/coverage/snapshots/parser_typescript.snap b/tasks/coverage/snapshots/parser_typescript.snap index 9d4ddf973a962..db4f832393baa 100644 --- a/tasks/coverage/snapshots/parser_typescript.snap +++ b/tasks/coverage/snapshots/parser_typescript.snap @@ -2871,13 +2871,12 @@ Expect to Parse: tasks/coverage/typescript/tests/cases/conformance/statements/Va · ── ╰──── - × Expected a semicolon or an implicit semicolon after a statement, but found none - ╭─[typescript/tests/cases/compiler/anonymousModules.ts:1:7] + × TS(1437): Module must be given a name. + ╭─[typescript/tests/cases/compiler/anonymousModules.ts:1:8] 1 │ module { - · ▲ + · ─ 2 │ export var foo = 1; ╰──── - help: Try inserting a semicolon here × Identifier `myFn` has already been declared ╭─[typescript/tests/cases/compiler/anyDeclare.ts:3:9] @@ -6392,13 +6391,12 @@ Expect to Parse: tasks/coverage/typescript/tests/cases/conformance/statements/Va 17 │ public pe:string; ╰──── - × Expected a semicolon or an implicit semicolon after a statement, but found none - ╭─[typescript/tests/cases/compiler/externModule.ts:1:8] + × TS(1437): Module must be given a name. + ╭─[typescript/tests/cases/compiler/externModule.ts:1:16] 1 │ declare module { - · ▲ + · ─ 2 │ export class XDate { ╰──── - help: Try inserting a semicolon here × Expected `,` or `)` but found `=>` ╭─[typescript/tests/cases/compiler/fatarrowfunctionsErrors.ts:2:8] @@ -6947,23 +6945,21 @@ Expect to Parse: tasks/coverage/typescript/tests/cases/conformance/statements/Va 3 │ } ╰──── - × Expected a semicolon or an implicit semicolon after a statement, but found none - ╭─[typescript/tests/cases/compiler/innerModExport1.ts:5:11] + × TS(1437): Module must be given a name. + ╭─[typescript/tests/cases/compiler/innerModExport1.ts:5:12] 4 │ var non_export_var: number; 5 │ module { - · ▲ + · ─ 6 │ var non_export_var = 0; ╰──── - help: Try inserting a semicolon here - × Expected a semicolon or an implicit semicolon after a statement, but found none - ╭─[typescript/tests/cases/compiler/innerModExport2.ts:5:11] + × TS(1437): Module must be given a name. + ╭─[typescript/tests/cases/compiler/innerModExport2.ts:5:12] 4 │ var non_export_var: number; 5 │ module { - · ▲ + · ─ 6 │ var non_export_var = 0; ╰──── - help: Try inserting a semicolon here × TS(1099): Type argument list cannot be empty. ╭─[typescript/tests/cases/compiler/instantiateTypeParameter.ts:2:13]