Skip to content
Closed
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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmmm I should git ignore but cache this in CI.

Binary file not shown.
25 changes: 25 additions & 0 deletions crates/oxc_parser/src/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -632,6 +632,31 @@ pub fn import_equals_can_only_be_used_in_typescript_files(span: Span) -> OxcDiag
ts_error("8002", "'import ... =' can only be used in TypeScript files.").with_label(span)
}

#[cold]
pub fn enum_can_only_be_used_in_typescript_files(span: Span) -> OxcDiagnostic {
ts_error("8003", "'enum' can only be used in TypeScript files.").with_label(span)
}

#[cold]
pub fn type_alias_can_only_be_used_in_typescript_files(span: Span) -> OxcDiagnostic {
ts_error("8004", "'type' alias can only be used in TypeScript files.").with_label(span)
}

#[cold]
pub fn interface_can_only_be_used_in_typescript_files(span: Span) -> OxcDiagnostic {
ts_error("8005", "'interface' can only be used in TypeScript files.").with_label(span)
}

#[cold]
pub fn module_can_only_be_used_in_typescript_files(span: Span) -> OxcDiagnostic {
ts_error("8006", "'module' can only be used in TypeScript files.").with_label(span)
}

#[cold]
pub fn namespace_can_only_be_used_in_typescript_files(span: Span) -> OxcDiagnostic {
ts_error("8007", "'namespace' can only be used in TypeScript files.").with_label(span)
}

#[cold]
pub fn index_signature_question_mark(span: Span) -> OxcDiagnostic {
ts_error("1019", "An index signature parameter cannot have a question mark.").with_label(span)
Expand Down
51 changes: 51 additions & 0 deletions crates/oxc_parser/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -820,4 +820,55 @@ mod test {
assert!(ret.errors.is_empty());
assert_eq!(ret.program.body.len(), 2);
}

#[test]
fn typescript_syntax_errors_in_js_files() {
let allocator = Allocator::default();
let js_source_type = SourceType::default().with_javascript(true);
let ts_source_type = SourceType::default().with_typescript(true);

// Test cases: TypeScript-only syntax that should error in JavaScript files
let test_cases = [
("export enum A {}", "enum"),
("export type B = {};", "type"),
("export interface C {}", "interface"),
("export module D {}", "module"),
("export namespace E {}", "namespace"),
];

for (source, expected_keyword) in test_cases {
// Should error in JavaScript files
let ret = Parser::new(&allocator, source, js_source_type).parse();
assert!(
!ret.errors.is_empty(),
"Expected error for '{expected_keyword}' in JavaScript file, but parsing succeeded"
);
assert!(
ret.errors[0].to_string().contains(&format!("'{expected_keyword}'"))
&& ret.errors[0].to_string().contains("TypeScript files"),
"Expected TypeScript-specific error for '{expected_keyword}', got: {}",
ret.errors[0]
);

// Should work fine in TypeScript files
let ret = Parser::new(&allocator, source, ts_source_type).parse();
assert!(
ret.errors.is_empty(),
"Unexpected error for '{expected_keyword}' in TypeScript file: {errors:?}",
errors = ret.errors
);
}
}

#[test]
fn standalone_typescript_syntax_still_errors_in_js() {
let allocator = Allocator::default();
let js_source_type = SourceType::default().with_javascript(true);

// Standalone enum should still error (this was already working)
let ret = Parser::new(&allocator, "enum A {}", js_source_type).parse();
assert!(!ret.errors.is_empty());
// This should be an "Unexpected token" error, not our TypeScript-specific error
assert!(ret.errors[0].to_string().contains("Unexpected token"));
}
}
43 changes: 40 additions & 3 deletions crates/oxc_parser/src/ts/statement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -421,13 +421,50 @@ impl<'a> ParserImpl<'a> {
match kind {
Kind::Global | Kind::Module | Kind::Namespace => {
let decl = self.parse_ts_module_declaration(start_span, modifiers);
if !self.is_ts {
match kind {
Kind::Module => {
self.error(diagnostics::module_can_only_be_used_in_typescript_files(
decl.span(),
));
}
Kind::Namespace => {
self.error(
diagnostics::namespace_can_only_be_used_in_typescript_files(
decl.span(),
),
);
}
_ => {} // Global is fine since it's a contextual keyword
}
}
Declaration::TSModuleDeclaration(decl)
}
Kind::Type => self.parse_ts_type_alias_declaration(start_span, modifiers),
Kind::Enum => self.parse_ts_enum_declaration(start_span, modifiers),
Kind::Type => {
let decl = self.parse_ts_type_alias_declaration(start_span, modifiers);
if !self.is_ts {
self.error(diagnostics::type_alias_can_only_be_used_in_typescript_files(
decl.span(),
));
}
decl
}
Kind::Enum => {
let decl = self.parse_ts_enum_declaration(start_span, modifiers);
if !self.is_ts {
self.error(diagnostics::enum_can_only_be_used_in_typescript_files(decl.span()));
}
decl
}
Kind::Interface => {
self.bump_any();
self.parse_ts_interface_declaration(start_span, modifiers)
let decl = self.parse_ts_interface_declaration(start_span, modifiers);
if !self.is_ts {
self.error(diagnostics::interface_can_only_be_used_in_typescript_files(
decl.span(),
));
}
decl
}
Kind::Class => {
let decl = self.parse_class_declaration(start_span, modifiers, decorators);
Expand Down