Skip to content

Commit

Permalink
feat(oxc)!: add SourceType::Unambiguous; parse .js as unambiguous
Browse files Browse the repository at this point in the history
See https://babel.dev/docs/options#misc-options for background on `unambiguous`

Once `SourceType::Unambiguous` is parsed, it will correctly set the
returned `Program::source_type` to either `module` or `script`.
  • Loading branch information
Boshen committed Sep 6, 2024
1 parent 28b934c commit b83d782
Show file tree
Hide file tree
Showing 9 changed files with 78 additions and 12 deletions.
1 change: 1 addition & 0 deletions crates/oxc_parser/src/js/expression.rs
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,7 @@ impl<'a> ParserImpl<'a> {
Kind::New => self.parse_new_expression(),
Kind::Super => Ok(self.parse_super()),
Kind::Import => {
self.set_source_type_to_module_if_unambiguous();
let span = self.start_span();
let identifier = self.parse_keyword_identifier(Kind::Import);
match self.cur_kind() {
Expand Down
3 changes: 3 additions & 0 deletions crates/oxc_parser/src/js/module.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ impl<'a> ParserImpl<'a> {
self.ctx = self.ctx.and_in(has_in);
self.bump(Kind::Comma);
self.expect(Kind::RParen)?;

Ok(self.ast.expression_import(self.end_span(span), expression, arguments))
}

Expand Down Expand Up @@ -209,6 +210,8 @@ impl<'a> ParserImpl<'a> {

/// [Exports](https://tc39.es/ecma262/#sec-exports)
pub(crate) fn parse_export_declaration(&mut self) -> Result<Statement<'a>> {
self.set_source_type_to_module_if_unambiguous();

let span = self.start_span();
self.bump_any(); // advance `export`

Expand Down
11 changes: 10 additions & 1 deletion crates/oxc_parser/src/js/statement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@ impl<'a> ParserImpl<'a> {
break;
}
let stmt = self.parse_statement_list_item(StatementContext::StatementList)?;

if is_top_level && stmt.is_module_declaration() {
self.set_source_type_to_module_if_unambiguous();
}

// Section 11.2.1 Directive Prologue
// The only way to get a correct directive is to parse the statement first and check if it is a string literal.
// All other method are flawed, see test cases in [babel](https://github.com/babel/babel/blob/main/packages/babel-parser/test/fixtures/core/categorized/not-directive/input.js)
Expand Down Expand Up @@ -94,9 +99,13 @@ impl<'a> ParserImpl<'a> {
Kind::Debugger => self.parse_debugger_statement(),
Kind::Class => self.parse_class_statement(stmt_ctx, start_span),
Kind::Import if !matches!(self.peek_kind(), Kind::Dot | Kind::LParen) => {
self.set_source_type_to_module_if_unambiguous();
self.parse_import_declaration()
}
Kind::Export => self.parse_export_declaration(),
Kind::Export => {
self.set_source_type_to_module_if_unambiguous();
self.parse_export_declaration()
}
// [+Return] ReturnStatement[?Yield, ?Await]
Kind::Return => self.parse_return_statement(),
Kind::Var => self.parse_variable_statement(stmt_ctx),
Expand Down
30 changes: 30 additions & 0 deletions crates/oxc_parser/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,8 @@ impl<'a> ParserImpl<'a> {
let (directives, statements) =
self.parse_directives_and_statements(/* is_top_level */ true)?;

self.set_source_type_to_script_if_unambiguous();

let span = Span::new(0, self.source_text.len() as u32);
Ok(self.ast.program(span, self.source_type, hashbang, directives, statements))
}
Expand Down Expand Up @@ -416,6 +418,18 @@ impl<'a> ParserImpl<'a> {
fn ts_enabled(&self) -> bool {
self.source_type.is_typescript()
}

fn set_source_type_to_module_if_unambiguous(&mut self) {
if self.source_type.is_unambiguous() {
self.source_type = self.source_type.with_module(true);
}
}

fn set_source_type_to_script_if_unambiguous(&mut self) {
if self.source_type.is_unambiguous() {
self.source_type = self.source_type.with_script(true);
}
}
}

#[cfg(test)]
Expand Down Expand Up @@ -511,6 +525,22 @@ mod test {
}
}

#[test]
fn unambiguous() {
let allocator = Allocator::default();
let source_type = SourceType::default().with_unambiguous(true);
assert!(source_type.is_unambiguous());
let sources =
["import x from 'foo';", "export {x} from 'foo';", "import('foo')", "import.meta"];
for source in sources {
let ret = Parser::new(&allocator, source, source_type).parse();
assert!(ret.program.source_type.is_module());
}

let ret = Parser::new(&allocator, "", source_type).parse();
assert!(ret.program.source_type.is_script());
}

#[test]
fn memory_leak() {
let allocator = Allocator::default();
Expand Down
4 changes: 4 additions & 0 deletions crates/oxc_semantic/src/checker/javascript.rs
Original file line number Diff line number Diff line change
Expand Up @@ -404,6 +404,10 @@ pub fn check_module_declaration<'a>(
let start = decl.span().start;
let span = Span::new(start, start + 6);
match ctx.source_type.module_kind() {
ModuleKind::Unambiguous => {
#[cfg(debug_assertions)]
panic!("Technically unreachable, omit to avoid panic.");
}
ModuleKind::Script => {
ctx.error(module_code(text, span));
}
Expand Down
23 changes: 18 additions & 5 deletions crates/oxc_span/src/source_type/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ impl SourceType {
pub const fn js() -> Self {
Self {
language: Language::JavaScript,
module_kind: ModuleKind::Script,
module_kind: ModuleKind::Unambiguous,
variant: LanguageVariant::Standard,
}
}
Expand Down Expand Up @@ -159,6 +159,10 @@ impl SourceType {
self.module_kind == ModuleKind::Module
}

pub fn is_unambiguous(self) -> bool {
self.module_kind == ModuleKind::Unambiguous
}

pub fn module_kind(self) -> ModuleKind {
self.module_kind
}
Expand Down Expand Up @@ -204,6 +208,14 @@ impl SourceType {
self
}

#[must_use]
pub const fn with_unambiguous(mut self, yes: bool) -> Self {
if yes {
self.module_kind = ModuleKind::Unambiguous;
}
self
}

#[must_use]
pub const fn with_typescript(mut self, yes: bool) -> Self {
if yes {
Expand Down Expand Up @@ -290,7 +302,8 @@ impl SourceType {
})?;

let (language, module_kind) = match extension {
"js" | "mjs" | "jsx" => (Language::JavaScript, ModuleKind::Module),
"js" => (Language::JavaScript, ModuleKind::Unambiguous),
"mjs" | "jsx" => (Language::JavaScript, ModuleKind::Module),
"cjs" => (Language::JavaScript, ModuleKind::Script),
"ts" if file_name.ends_with(".d.ts") => {
(Language::TypeScriptDefinition, ModuleKind::Module)
Expand Down Expand Up @@ -417,15 +430,15 @@ mod tests {
assert!(!ty.is_typescript(), "{ty:?}");
}

assert_eq!(SourceType::js().with_jsx(true).with_module(true), js);
assert_eq!(SourceType::js().with_jsx(true).with_unambiguous(true), js);
assert_eq!(SourceType::jsx().with_module(true), jsx);

assert!(js.is_module());
assert!(js.is_unambiguous());
assert!(mjs.is_module());
assert!(cjs.is_script());
assert!(jsx.is_module());

assert!(js.is_strict());
assert!(!js.is_strict());
assert!(mjs.is_strict());
assert!(!cjs.is_strict());
assert!(jsx.is_strict());
Expand Down
3 changes: 3 additions & 0 deletions crates/oxc_span/src/source_type/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ pub enum ModuleKind {
Script = 0,
/// ES6 Module
Module = 1,
/// Consider the file a "module" if import/export statements are present, or else consider it a "script".
/// See <https://babel.dev/docs/options#misc-options>
Unambiguous = 2,
}

/// JSX for JavaScript and TypeScript
Expand Down
2 changes: 2 additions & 0 deletions tasks/coverage/misc/fail/oxc.js
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
'use strict';

let.a = 1;
let()[a] = 1;
13 changes: 7 additions & 6 deletions tasks/coverage/parser_misc.snap
Original file line number Diff line number Diff line change
Expand Up @@ -245,15 +245,16 @@ Negative Passed: 17/17 (100.00%)
╰────

× The keyword 'let' is reserved
╭─[misc/fail/oxc.js:1:1]
1let.a = 1;
╭─[misc/fail/oxc.js:3:1]
2
3let.a = 1;
· ───
2let()[a] = 1;
4let()[a] = 1;
╰────

× The keyword 'let' is reserved
╭─[misc/fail/oxc.js:2:1]
1let.a = 1;
2let()[a] = 1;
╭─[misc/fail/oxc.js:4:1]
3let.a = 1;
4let()[a] = 1;
· ───
╰────

0 comments on commit b83d782

Please sign in to comment.