diff --git a/crates/swc/tests/rust_api.rs b/crates/swc/tests/rust_api.rs index 531bd0849d55..9ac34b62da87 100644 --- a/crates/swc/tests/rust_api.rs +++ b/crates/swc/tests/rust_api.rs @@ -132,6 +132,7 @@ fn shopify_2_same_opt() { syntax: Some(Syntax::Typescript(TsConfig { tsx: true, decorators: false, + disallow_assert_keywords: false, dts: false, no_early_errors: false, disallow_ambiguous_jsx_like: false, diff --git a/crates/swc/tests/tsc.rs b/crates/swc/tests/tsc.rs index b3cdced049c5..22f897791b87 100644 --- a/crates/swc/tests/tsc.rs +++ b/crates/swc/tests/tsc.rs @@ -386,6 +386,7 @@ fn matrix(input: &Path) -> Vec { syntax: Some(Syntax::Typescript(TsConfig { tsx: is_jsx, decorators, + disallow_assert_keywords: false, dts: false, no_early_errors: false, disallow_ambiguous_jsx_like: false, diff --git a/crates/swc_ecma_parser/src/error.rs b/crates/swc_ecma_parser/src/error.rs index 1715555dd478..8c11e050b6ed 100644 --- a/crates/swc_ecma_parser/src/error.rs +++ b/crates/swc_ecma_parser/src/error.rs @@ -291,6 +291,8 @@ pub enum SyntaxError { ReservedTypeAssertion, ReservedArrowTypeParam, + + InvalidAssertKeywords, } impl SyntaxError { @@ -754,6 +756,9 @@ impl SyntaxError { as in `() => ...`." .into(), SyntaxError::InvalidAssignTarget => "Invalid assignment target".into(), + SyntaxError::InvalidAssertKeywords => "The `assert` keyword is disallowed. Use the \ + `with` keyword instead for import attributes" + .into(), } } } diff --git a/crates/swc_ecma_parser/src/lib.rs b/crates/swc_ecma_parser/src/lib.rs index 260ea56e44e1..f1501cf83299 100644 --- a/crates/swc_ecma_parser/src/lib.rs +++ b/crates/swc_ecma_parser/src/lib.rs @@ -187,6 +187,20 @@ impl Syntax { } } + pub fn disallow_assert_keywords(self) -> bool { + match self { + Syntax::Es(EsConfig { + disallow_assert_keywords, + .. + }) => disallow_assert_keywords, + #[cfg(feature = "typescript")] + Syntax::Typescript(TsConfig { + disallow_assert_keywords, + .. + }) => disallow_assert_keywords, + } + } + /// Should we parse jsx? pub fn jsx(self) -> bool { match self { @@ -315,6 +329,9 @@ pub struct TsConfig { #[serde(default)] pub decorators: bool, + #[serde(default)] + pub disallow_assert_keywords: bool, + /// `.d.ts` #[serde(skip, default)] pub dts: bool, @@ -360,6 +377,9 @@ pub struct EsConfig { #[serde(default, alias = "importAssertions")] pub import_attributes: bool, + #[serde(default)] + pub disallow_assert_keywords: bool, + #[serde(default, rename = "allowSuperOutsideMethod")] pub allow_super_outside_method: bool, diff --git a/crates/swc_ecma_parser/src/parser/stmt/module_item.rs b/crates/swc_ecma_parser/src/parser/stmt/module_item.rs index 290bf098b07d..8a6980ca2626 100644 --- a/crates/swc_ecma_parser/src/parser/stmt/module_item.rs +++ b/crates/swc_ecma_parser/src/parser/stmt/module_item.rs @@ -54,13 +54,25 @@ impl Parser { _ => unreachable!(), }; let _ = cur!(self, false); + let with_start = cur_pos!(self); let with = if self.input.syntax().import_attributes() && !self.input.had_line_break_before_cur() - && (eat!(self, "assert") || eat!(self, "with")) { - match *self.parse_object::>()? { - Expr::Object(v) => Some(Box::new(v)), - _ => unreachable!(), + if eat!(self, "assert") { + if self.input.syntax().disallow_assert_keywords() { + self.emit_err(span!(self, with_start), SyntaxError::InvalidAssertKeywords); + } + match *self.parse_object::>()? { + Expr::Object(v) => Some(Box::new(v)), + _ => unreachable!(), + } + } else if eat!(self, "with") { + match *self.parse_object::>()? { + Expr::Object(v) => Some(Box::new(v)), + _ => unreachable!(), + } + } else { + None } } else { None @@ -178,17 +190,28 @@ impl Parser { }; let _ = cur!(self, false); - let with = if self.input.syntax().import_attributes() - && !self.input.had_line_break_before_cur() - && (eat!(self, "assert") || eat!(self, "with")) - { - match *self.parse_object::>()? { - Expr::Object(v) => Some(Box::new(v)), - _ => unreachable!(), - } - } else { - None - }; + let with_start = cur_pos!(self); + let with = + if self.input.syntax().import_attributes() && !self.input.had_line_break_before_cur() { + if eat!(self, "assert") { + if self.input.syntax().disallow_assert_keywords() { + self.emit_err(span!(self, with_start), SyntaxError::InvalidAssertKeywords); + } + match *self.parse_object::>()? { + Expr::Object(v) => Some(Box::new(v)), + _ => unreachable!(), + } + } else if eat!(self, "with") { + match *self.parse_object::>()? { + Expr::Object(v) => Some(Box::new(v)), + _ => unreachable!(), + } + } else { + None + } + } else { + None + }; expect!(self, ';'); @@ -847,17 +870,28 @@ impl Parser { _ => unexpected!(self, "a string literal"), }; let _ = cur!(self, false); - let with = if self.input.syntax().import_attributes() - && !self.input.had_line_break_before_cur() - && (eat!(self, "assert") || eat!(self, "with")) - { - match *self.parse_object::>()? { - Expr::Object(v) => Some(Box::new(v)), - _ => unreachable!(), - } - } else { - None - }; + let with_start = cur_pos!(self); + let with = + if self.input.syntax().import_attributes() && !self.input.had_line_break_before_cur() { + if eat!(self, "assert") { + if self.input.syntax().disallow_assert_keywords() { + self.emit_err(span!(self, with_start), SyntaxError::InvalidAssertKeywords); + } + match *self.parse_object::>()? { + Expr::Object(v) => Some(Box::new(v)), + _ => unreachable!(), + } + } else if eat!(self, "with") { + match *self.parse_object::>()? { + Expr::Object(v) => Some(Box::new(v)), + _ => unreachable!(), + } + } else { + None + } + } else { + None + }; expect!(self, ';'); Ok((src, with)) } diff --git a/crates/swc_ecma_parser/src/parser/tests.rs b/crates/swc_ecma_parser/src/parser/tests.rs index d1d554c47aa9..65736494a9e8 100644 --- a/crates/swc_ecma_parser/src/parser/tests.rs +++ b/crates/swc_ecma_parser/src/parser/tests.rs @@ -318,6 +318,7 @@ fn illegal_language_mode_directive1() { }, ); } + #[test] fn illegal_language_mode_directive2() { test_parser( @@ -348,3 +349,62 @@ fn illegal_language_mode_directive2() { fn parse_non_strict_for_loop() { script("for (var v1 = 1 in v3) {}"); } + +#[test] +fn disallow_assert_keywords1() { + test_parser( + r#"import foo from "foo.json" assert { type: "json" }"#, + Syntax::Es(EsConfig { + import_attributes: true, + disallow_assert_keywords: true, + ..Default::default() + }), + |p| { + let program = p.parse_program()?; + + let errors = p.take_errors(); + assert_eq!( + errors, + vec![Error::new( + Span { + lo: BytePos(28), + hi: BytePos(34), + ctxt: swc_common::SyntaxContext::empty() + }, + crate::parser::SyntaxError::InvalidAssertKeywords + )] + ); + + Ok(program) + }, + ); +} + +#[test] +fn disallow_assert_keywords2() { + test_parser( + r#"import foo from "foo.json" assert { type: "json" }"#, + Syntax::Typescript(TsConfig { + disallow_assert_keywords: true, + ..Default::default() + }), + |p| { + let program = p.parse_program()?; + + let errors = p.take_errors(); + assert_eq!( + errors, + vec![Error::new( + Span { + lo: BytePos(28), + hi: BytePos(34), + ctxt: swc_common::SyntaxContext::empty() + }, + crate::parser::SyntaxError::InvalidAssertKeywords + )] + ); + + Ok(program) + }, + ); +} diff --git a/crates/swc_ecma_transforms_typescript/tests/strip_correctness.rs b/crates/swc_ecma_transforms_typescript/tests/strip_correctness.rs index 38debe9b86c7..0aea256efc2f 100644 --- a/crates/swc_ecma_transforms_typescript/tests/strip_correctness.rs +++ b/crates/swc_ecma_transforms_typescript/tests/strip_correctness.rs @@ -90,6 +90,7 @@ fn identity(entry: PathBuf) { Syntax::Typescript(TsConfig { tsx: file_name.contains("tsx"), decorators: true, + disallow_assert_keywords: false, dts: false, no_early_errors: false, disallow_ambiguous_jsx_like: false, diff --git a/packages/types/index.ts b/packages/types/index.ts index dbaebf256be4..bccf593adca9 100644 --- a/packages/types/index.ts +++ b/packages/types/index.ts @@ -675,6 +675,10 @@ export interface TsParserConfig { * @deprecated Always true because it's in ecmascript spec. */ dynamicImport?: boolean; + /** + * Defaults to `false` + */ + disallowAssertKeywords?: boolean; } export interface EsParserConfig { @@ -763,6 +767,10 @@ export interface EsParserConfig { * Defaults to `false` */ explicitResourceManagement?: boolean; + /** + * Defaults to `false` + */ + disallowAssertKeywords?: boolean; } /**