diff --git a/.changeset/rewrite-parser-api.md b/.changeset/rewrite-parser-api.md new file mode 100644 index 000000000000..efa43b290d29 --- /dev/null +++ b/.changeset/rewrite-parser-api.md @@ -0,0 +1,29 @@ +--- +dbg-swc: major +jsdoc: patch +swc: major +swc_bundler: major +swc_compiler_base: major +swc_core: major +swc_ecma_codegen: patch +swc_ecma_lints: patch +swc_ecma_minifier: major +swc_ecma_parser: major +swc_ecma_preset_env: patch +swc_ecma_quote_macros: major +swc_ecma_react_compiler: major +swc_ecma_transforms_base: major +swc_ecma_transforms_module: major +swc_ecma_transforms_optimization: major +swc_ecma_transforms_react: major +swc_ecma_transforms_testing: major +swc_ecma_transforms_typescript: patch +swc_ecma_utils: patch +swc_estree_compat: major +swc_html_minifier: major +swc_node_bundler: major +swc_ts_fast_strip: major +swc_typescript: patch +--- + +feat(es/parser): Replace the parser API with the OXC-style entry point and remove the abandoned lexer crate diff --git a/Cargo.lock b/Cargo.lock index 505b65f0bcc8..01be262921c0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5360,12 +5360,6 @@ dependencies = [ "serde_core", ] -[[package]] -name = "seq-macro" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bc711410fbe7399f390ca1c3b60ad0f53f80e95c5eb935e52268a0e2cd49acc" - [[package]] name = "serde" version = "1.0.228" @@ -6511,28 +6505,6 @@ dependencies = [ "swc_ecma_visit", ] -[[package]] -name = "swc_ecma_lexer" -version = "40.0.1" -dependencies = [ - "bitflags 2.10.0", - "compact_str", - "either", - "num-bigint", - "rustc-hash 2.1.1", - "seq-macro", - "serde", - "smallvec", - "stacker", - "swc_atoms", - "swc_common", - "swc_ecma_ast", - "swc_ecma_parser", - "swc_ecma_visit", - "testing", - "tracing", -] - [[package]] name = "swc_ecma_lints" version = "33.0.0" @@ -6628,14 +6600,10 @@ dependencies = [ "bitflags 2.10.0", "cbor4ii", "codspeed-criterion-compat", - "compact_str", - "either", "num-bigint", "pathdiff", - "phf", "pretty_assertions", "rustc-hash 2.1.1", - "seq-macro", "serde", "serde_json", "stacker", @@ -6645,7 +6613,6 @@ dependencies = [ "swc_ecma_visit", "swc_malloc", "testing", - "tracing", "walkdir", ] diff --git a/Cargo.toml b/Cargo.toml index 31b8fce2c691..c5fee675946c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -98,7 +98,6 @@ rustc-hash = "2" ryu-js = "1.0.0" scoped-tls = "1.0.1" semver = "1.0.20" -seq-macro = "0.3" serde = "1.0.225" serde-wasm-bindgen = "0.6.5" serde_derive = "1.0.225" diff --git a/bindings/binding_core_wasm/__tests__/error.js b/bindings/binding_core_wasm/__tests__/error.js index 98867d92a761..925055771e9f 100644 --- a/bindings/binding_core_wasm/__tests__/error.js +++ b/bindings/binding_core_wasm/__tests__/error.js @@ -7,5 +7,5 @@ it("properly reports error", () => { expect(() => { swc.transformSync("Foo {}", {}); - }).toThrow("Expected ';', '}' or "); + }).toThrow(); }); diff --git a/bindings/binding_es_ast_viewer/Cargo.toml b/bindings/binding_es_ast_viewer/Cargo.toml index d3463ac5349f..545e93daa124 100644 --- a/bindings/binding_es_ast_viewer/Cargo.toml +++ b/bindings/binding_es_ast_viewer/Cargo.toml @@ -17,7 +17,7 @@ features = [ "common", "ecma_visit", "ecma_transforms", - "ecma_parser_unstable", + "ecma_parser_typescript", ] path = "../../crates/swc_core" diff --git a/bindings/binding_es_ast_viewer/__tests__/parse.js b/bindings/binding_es_ast_viewer/__tests__/parse.js index 7d453c1a5c1a..507372f9f934 100644 --- a/bindings/binding_es_ast_viewer/__tests__/parse.js +++ b/bindings/binding_es_ast_viewer/__tests__/parse.js @@ -14,7 +14,7 @@ describe("parse", () => { expect(result[0]).toMatch(/Module|Script/); expect(result[0]).toContain("Stmt"); // Second element should be the tokens - expect(result[1]).toContain("TokenAndSpan"); + expect(result[1]).toContain("PackedToken"); }); it("should parse TypeScript code with type annotations", () => { @@ -25,7 +25,7 @@ describe("parse", () => { expect(result[0]).toMatch(/Module|Script/); expect(result[0]).toContain("TsTypeAnn"); // Should have tokens - expect(result[1]).toContain("TokenAndSpan"); + expect(result[1]).toContain("PackedToken"); }); it("should parse JSX code", () => { @@ -70,7 +70,7 @@ describe("parse", () => { expect(result).toHaveLength(2); expect(result[0]).toContain("ArrowExpr"); // Check for arrow token - expect(result[1]).toContain("=>"); + expect(result[1]).toContain("kind: Arrow"); }); it("should parse async/await", () => { @@ -80,7 +80,7 @@ describe("parse", () => { expect(result[0]).toContain("AwaitExpr"); expect(result[0]).toContain("FnDecl"); // Tokens should be present - expect(result[1]).toContain("TokenAndSpan"); + expect(result[1]).toContain("PackedToken"); }); it("should parse class declarations", () => { @@ -90,7 +90,7 @@ describe("parse", () => { expect(result[0]).toContain("ClassDecl"); expect(result[0]).toContain("Constructor"); // Check for class keyword in tokens - expect(result[1]).toContain("class"); + expect(result[1]).toContain("kind: Class"); }); it("should parse template literals", () => { @@ -99,7 +99,7 @@ describe("parse", () => { expect(result).toHaveLength(2); expect(result[0]).toContain("Tpl"); // Check for template tokens - expect(result[1]).toContain("`"); + expect(result[1]).toContain("kind: TemplateHead"); }); it("should handle syntax errors gracefully", () => { @@ -169,7 +169,7 @@ describe("parse", () => { expect(result).toHaveLength(2); expect(result[0]).toContain("SpreadElement"); // Check for spread token - expect(result[1]).toContain("..."); + expect(result[1]).toContain("kind: DotDotDot"); }); it("should parse optional chaining", () => { @@ -187,7 +187,7 @@ describe("parse", () => { expect(result[0]).toContain("BinExpr"); expect(result[0]).toContain('op: "??"'); // Check for nullish coalescing token - expect(result[1]).toContain("??"); + expect(result[1]).toContain("kind: NullishCoalescing"); }); it("should parse BigInt literals", () => { @@ -196,7 +196,7 @@ describe("parse", () => { expect(result).toHaveLength(2); expect(result[0]).toContain("BigInt"); // Check for BigInt token - expect(result[1]).toContain(""); + expect(result[1]).toContain("kind: BigInt"); }); it("should parse private class fields", () => { diff --git a/bindings/binding_es_ast_viewer/src/lib.rs b/bindings/binding_es_ast_viewer/src/lib.rs index 79324201a881..4916f9997f6a 100644 --- a/bindings/binding_es_ast_viewer/src/lib.rs +++ b/bindings/binding_es_ast_viewer/src/lib.rs @@ -6,7 +6,7 @@ use swc_core::{ common::{errors::ColorConfig, FileName, Globals, Mark, SourceMap, GLOBALS}, ecma::{ ast::*, - parser::{unstable::Capturing, EsSyntax, Lexer, Parser, StringInput, Syntax, TsSyntax}, + parser::{ModuleKind, ParseOptions, Parser, ParserReturn, SourceType}, transforms::base::resolver, visit::VisitMutWith, }, @@ -46,8 +46,11 @@ pub fn parse(input: &str, file_name: Option) -> Result, Stri .map(|e| e == "ts" || e == "tsx" || e == "mts" || e == "cts" || e == "mtsx" || e == "ctsx") .unwrap_or_default(); - let is_d_ts = - is_ts && matches!(iter.next(), Some("d" | "D")) || matches!(iter.next(), Some("d" | "D")); + let lower_file_name = file_name.to_ascii_lowercase(); + let is_d_ts = is_ts + && [".d.ts", ".d.mts", ".d.cts"] + .iter() + .any(|suffix| lower_file_name.ends_with(suffix)); let cm: Arc = Default::default(); let fm = cm.new_source_file( @@ -55,31 +58,41 @@ pub fn parse(input: &str, file_name: Option) -> Result, Stri input.to_string(), ); - let syntax = if is_ts { - Syntax::Typescript(TsSyntax { - tsx: is_jsx, - dts: is_d_ts, - ..Default::default() - }) + let source_type = if is_d_ts { + SourceType::type_definition() + } else if is_ts && is_jsx { + SourceType::tsx() + } else if is_ts { + SourceType::typescript() + } else if is_jsx { + SourceType::jsx() } else { - Syntax::Es(EsSyntax { - jsx: is_jsx, - ..Default::default() - }) + SourceType::unambiguous() }; - let target = EsVersion::latest(); - - let lexer = Capturing::new(Lexer::new(syntax, target, StringInput::from(&*fm), None)); - let mut parser = Parser::new_from(lexer); - - let program = if is_esm { - parser.parse_module().map(Program::Module) + let module_kind = if is_esm { + ModuleKind::Module } else if is_cjs { - parser.parse_commonjs().map(Program::Script) + ModuleKind::CommonJs } else { - parser.parse_program() + ModuleKind::Unambiguous + }; + let source_type = source_type.with_module_kind(module_kind); + let options = ParseOptions { + target: EsVersion::latest(), + ..Default::default() }; + let ParserReturn { + program, + diagnostics, + tokens, + panicked, + .. + } = Parser::new(input, source_type) + .with_options(options) + .with_start_pos(fm.start_pos) + .with_tokens() + .parse(); let mut ast = try_with_handler( cm, @@ -88,20 +101,19 @@ pub fn parse(input: &str, file_name: Option) -> Result, Stri ..Default::default() }, |handler| { - for err in parser.take_errors() { + for err in diagnostics { err.into_diagnostic(handler).emit(); } - program.map_err(|err| { - err.into_diagnostic(handler).emit(); - anyhow::anyhow!("Failed to parse the input") - }) + if panicked || handler.has_errors() { + Err(anyhow::anyhow!("Failed to parse the input")) + } else { + Ok(program) + } }, ) .map_err(|err| err.to_pretty_string())?; - let tokens = parser.input_mut().iter_mut().take(); - GLOBALS.set(&Globals::default(), || { let unresolved_mark = Mark::new(); let top_level_mark = Mark::new(); diff --git a/bindings/binding_react_compiler_node/src/support.rs b/bindings/binding_react_compiler_node/src/support.rs index 9d3e2f2b336e..e1296c8e9673 100644 --- a/bindings/binding_react_compiler_node/src/support.rs +++ b/bindings/binding_react_compiler_node/src/support.rs @@ -1,8 +1,5 @@ use napi::bindgen_prelude::*; -use swc_core::{ - common::{sync::Lrc, FileName, SourceMap}, - ecma::{ast::EsVersion, parser::Syntax}, -}; +use swc_core::ecma::parser::{ParseOptions, Parser, SourceType}; struct IsReactCompilerRequiredTask { code: String, @@ -23,26 +20,17 @@ impl Task for IsReactCompilerRequiredTask { } fn is_react_compiler_required_inner(code: &str) -> bool { - let cm = Lrc::new(SourceMap::default()); - let fm = cm.new_source_file(FileName::Anon.into(), code.to_string()); - - let program = swc_core::ecma::parser::parse_file_as_program( - &fm, - Syntax::Typescript(swc_core::ecma::parser::TsSyntax { + let result = Parser::new(code, SourceType::tsx()) + .with_options(ParseOptions { decorators: true, - tsx: true, - ..Default::default() - }), - EsVersion::latest(), - None, - &mut vec![], - ); - - let Ok(program) = program else { + ..ParseOptions::default() + }) + .parse(); + if result.panicked || !result.diagnostics.is_empty() { return false; - }; + } - swc_ecma_react_compiler::fast_check::is_required(&program) + swc_ecma_react_compiler::fast_check::is_required(&result.program) } #[napi] diff --git a/bindings/binding_typescript_wasm/__tests__/__snapshots__/transform.js.snap b/bindings/binding_typescript_wasm/__tests__/__snapshots__/transform.js.snap index bf54b3746aa5..c1a09f80c06b 100644 --- a/bindings/binding_typescript_wasm/__tests__/__snapshots__/transform.js.snap +++ b/bindings/binding_typescript_wasm/__tests__/__snapshots__/transform.js.snap @@ -6,7 +6,7 @@ exports[`transform > in strip-only mode > should not emit 'Caused by: failed to "endColumn": 22, "endLine": 1, "filename": "test.ts", - "message": "await isn't allowed in non-async function", + "message": "Expected 'Ident', got 'Await'", "snippet": "function foo() { await Promise.resolve(1); } ^^^^^ ", @@ -47,7 +47,7 @@ exports[`transform > in strip-only mode > should report correct error for syntax "endColumn": 31, "endLine": 1, "filename": "test.ts", - "message": "Expected ';', '}' or ", + "message": "Expected 'Semi', got 'Ident'", "snippet": "function foo() { invalid syntax } ^^^^^^ ", diff --git a/bindings/binding_typescript_wasm/__tests__/transform.js b/bindings/binding_typescript_wasm/__tests__/transform.js index ebe097fec991..56eb96626f6e 100644 --- a/bindings/binding_typescript_wasm/__tests__/transform.js +++ b/bindings/binding_typescript_wasm/__tests__/transform.js @@ -31,7 +31,7 @@ describe("transform", () => { {}, ), ).resolves.toMatchSnapshot(); - expect( + await expect( swc.transform( `declare enum Foo { a = 2, diff --git a/crates/dbg-swc/src/es/flow/strip.rs b/crates/dbg-swc/src/es/flow/strip.rs index cf560644d59d..c0155c65ca4e 100644 --- a/crates/dbg-swc/src/es/flow/strip.rs +++ b/crates/dbg-swc/src/es/flow/strip.rs @@ -9,9 +9,7 @@ use clap::Args; use swc_common::{FileName, Mark, SourceMap}; use swc_ecma_ast::{EsVersion, Program}; use swc_ecma_codegen::{text_writer::JsWriter, Config as CodegenConfig, Emitter}; -use swc_ecma_parser::{ - error::Error as ParseError, parse_file_as_program, EsSyntax, FlowSyntax, Syntax, -}; +use swc_ecma_parser::{error::Error as ParseError, EsSyntax, FlowSyntax, Syntax}; use swc_ecma_transforms_base::{fixer::fixer, resolver}; use swc_ecma_transforms_typescript::typescript; @@ -150,27 +148,20 @@ fn verify_file( ) })?; - let mut parse_recovered_errors = Vec::new(); - let parsed = parse_file_as_program( - &fm, + let (source_type, options) = swc_ecma_parser::SourceType::from_legacy( Syntax::Flow(flow_syntax), + swc_ecma_parser::ModuleKind::Unambiguous, EsVersion::latest(), - None, - &mut parse_recovered_errors, - ) - .map_err(|err| { - FlowStripFailure::new( - path, - FailureStage::Parse, - parse_error_message(&err, &parse_recovered_errors), - ) - })?; - - if !parse_recovered_errors.is_empty() { + ); + let parsed = swc_ecma_parser::Parser::new(&fm.src, source_type) + .with_options(options) + .with_start_pos(fm.start_pos) + .parse(); + if !parsed.diagnostics.is_empty() { return Err(FlowStripFailure::new( path, FailureStage::Parse, - recovered_parse_message(&parse_recovered_errors), + recovered_parse_message(&parsed.diagnostics), )); } @@ -178,6 +169,7 @@ fn verify_file( let top_level_mark = Mark::new(); let transformed = parsed + .program .apply(resolver(unresolved_mark, top_level_mark, false)) .apply(typescript::typescript( typescript::Config { @@ -201,45 +193,26 @@ fn verify_file( } let output_fm = cm.new_source_file(FileName::Anon.into(), output); - let mut reparse_recovered_errors = Vec::new(); - parse_file_as_program( - &output_fm, + let (source_type, options) = swc_ecma_parser::SourceType::from_legacy( Syntax::Es(es_reparse_syntax(flow_syntax.jsx)), + swc_ecma_parser::ModuleKind::Unambiguous, EsVersion::latest(), - None, - &mut reparse_recovered_errors, - ) - .map_err(|err| { - FlowStripFailure::new( - path, - FailureStage::Reparse, - parse_error_message(&err, &reparse_recovered_errors), - ) - })?; - - if !reparse_recovered_errors.is_empty() { + ); + let reparsed = swc_ecma_parser::Parser::new(&output_fm.src, source_type) + .with_options(options) + .with_start_pos(output_fm.start_pos) + .parse(); + if !reparsed.diagnostics.is_empty() { return Err(FlowStripFailure::new( path, FailureStage::Reparse, - recovered_parse_message(&reparse_recovered_errors), + recovered_parse_message(&reparsed.diagnostics), )); } Ok(()) } -fn parse_error_message(primary: &ParseError, recovered: &[ParseError]) -> String { - let mut message = format!("{primary:?}"); - if let Some(first_recovered) = recovered.first() { - message.push_str("; recovered: "); - message.push_str(&format!("{first_recovered:?}")); - if recovered.len() > 1 { - message.push_str(&format!(" (+{} more)", recovered.len() - 1)); - } - } - message -} - fn recovered_parse_message(recovered: &[ParseError]) -> String { let first = recovered .first() diff --git a/crates/dbg-swc/src/util/mod.rs b/crates/dbg-swc/src/util/mod.rs index b224170b6cdb..1f43faccb64f 100644 --- a/crates/dbg-swc/src/util/mod.rs +++ b/crates/dbg-swc/src/util/mod.rs @@ -8,9 +8,9 @@ use std::{ use anyhow::{bail, Context, Result}; use flate2::{write::ZlibEncoder, Compression}; use swc_common::{comments::SingleThreadedComments, errors::HANDLER, Mark, SourceFile, SourceMap}; -use swc_ecma_ast::{EsVersion, Module}; +use swc_ecma_ast::{EsVersion, Module, Program}; use swc_ecma_codegen::text_writer::{omit_trailing_semi, JsWriter, WriteJs}; -use swc_ecma_parser::{parse_file_as_module, Syntax}; +use swc_ecma_parser::Syntax; use swc_ecma_transforms_base::resolver; use swc_ecma_visit::VisitMutWith; @@ -49,24 +49,35 @@ pub fn parse_js(fm: Arc) -> Result { let unresolved_mark = Mark::new(); let top_level_mark = Mark::new(); - let mut errors = Vec::new(); let comments = SingleThreadedComments::default(); - let res = parse_file_as_module( - &fm, + let (source_type, options) = swc_ecma_parser::SourceType::from_legacy( Syntax::Es(Default::default()), + swc_ecma_parser::ModuleKind::Module, EsVersion::latest(), - Some(&comments), - &mut errors, - ) - .map_err(|err| HANDLER.with(|handler| err.into_diagnostic(handler).emit())); - - for err in errors { + ); + let parsed = swc_ecma_parser::Parser::new(&fm.src, source_type) + .with_options(options) + .with_start_pos(fm.start_pos) + .with_tokens() + .parse(); + let has_errors = !parsed.diagnostics.is_empty(); + for err in parsed.diagnostics { HANDLER.with(|handler| err.into_diagnostic(handler).emit()); } - - let mut m = match res { - Ok(v) => v, - Err(()) => bail!("failed to parse a js file as a module"), + if parsed.panicked || has_errors { + bail!("failed to parse a js file as a module"); + } + swc_ecma_parser::attach_comments( + &fm.src, + fm.start_pos, + &comments, + parsed.comments, + &parsed.tokens, + &parsed.program, + ); + let mut m = match parsed.program { + Program::Module(module) => module, + Program::Script(_) => unreachable!("module source type produced a script"), }; m.visit_mut_with(&mut resolver(unresolved_mark, top_level_mark, false)); diff --git a/crates/jsdoc/tests/fixture.rs b/crates/jsdoc/tests/fixture.rs index db42cec14881..4527b35c458a 100644 --- a/crates/jsdoc/tests/fixture.rs +++ b/crates/jsdoc/tests/fixture.rs @@ -5,7 +5,7 @@ use swc_common::{ comments::{Comment, CommentKind, Comments}, BytePos, DUMMY_SP, }; -use swc_ecma_parser::{parse_file_as_module, EsSyntax, Syntax}; +use swc_ecma_parser::{attach_comments, Parser, SourceType}; use testing::NormalizedOutput; #[testing::fixture("tests/fixtures/**/*.js")] @@ -15,22 +15,20 @@ fn fixture(path: PathBuf) { let fm = cm.load_file(&path).expect("failed to load fixture file"); - let mut errors = Vec::new(); - - if let Err(err) = parse_file_as_module( - &fm, - Syntax::Es(EsSyntax { - jsx: true, - ..Default::default() - }), - Default::default(), - Some(&comments), - &mut errors, - ) { - err.into_diagnostic(&handler).emit(); - } - for err in errors { - err.into_diagnostic(&handler).emit(); + let mut result = Parser::new(&fm.src, SourceType::jsx()) + .with_start_pos(fm.start_pos) + .with_tokens() + .parse(); + attach_comments( + &fm.src, + fm.start_pos, + &comments, + std::mem::take(&mut result.comments), + &result.tokens, + &result.program, + ); + for error in result.diagnostics { + error.into_diagnostic(&handler).emit(); } if handler.has_errors() { return Err(()); diff --git a/crates/swc-ast-explorer/src/parser.rs b/crates/swc-ast-explorer/src/parser.rs index b3b05e128e0d..b0ad0dc8d7c0 100644 --- a/crates/swc-ast-explorer/src/parser.rs +++ b/crates/swc-ast-explorer/src/parser.rs @@ -1,7 +1,7 @@ -use anyhow::{anyhow, bail, Result}; +use anyhow::{bail, Result}; use swc_common::{errors::Handler, SourceFile}; use swc_ecma_ast::{EsVersion, Program}; -use swc_ecma_parser::{parse_file_as_program, Syntax, TsSyntax}; +use swc_ecma_parser::{ModuleKind, Parser, SourceType, Syntax, TsSyntax}; /// Parses a source file as JavaScript, TypeScript, or TSX and returns the SWC /// program tree. Recovered parser errors are treated as fatal so the CLI does @@ -11,22 +11,21 @@ pub fn parse_source(file: &SourceFile, handler: &Handler) -> Result { tsx: true, ..Default::default() }); - let mut errors = Vec::new(); - let program = parse_file_as_program(file, syntax, EsVersion::latest(), None, &mut errors) - .map_err(|err| { - err.into_diagnostic(handler).emit(); - anyhow!("Syntax Error") - })?; + let (source_type, options) = + SourceType::from_legacy(syntax, ModuleKind::Unambiguous, EsVersion::latest()); + let parsed = Parser::new(&file.src, source_type) + .with_options(options) + .with_start_pos(file.start_pos) + .parse(); - let mut has_recovered_error = false; - for err in errors { + let has_errors = !parsed.diagnostics.is_empty(); + for err in parsed.diagnostics { err.into_diagnostic(handler).emit(); - has_recovered_error = true; } - if has_recovered_error { + if parsed.panicked || has_errors { bail!("Syntax Error"); } - Ok(program) + Ok(parsed.program) } diff --git a/crates/swc/benches/isolated_declarations.rs b/crates/swc/benches/isolated_declarations.rs index 82048d5e1b10..10c866d737c2 100644 --- a/crates/swc/benches/isolated_declarations.rs +++ b/crates/swc/benches/isolated_declarations.rs @@ -2,7 +2,7 @@ use std::{path::Path, sync::Arc}; use criterion::{criterion_group, criterion_main, Criterion}; use swc_common::{comments::SingleThreadedComments, FilePathMapping, Mark, SourceMap, GLOBALS}; -use swc_ecma_parser::{parse_file_as_program, Syntax}; +use swc_ecma_parser::{attach_comments, Parser, SourceType}; use swc_ecma_transforms::{fixer::paren_remover, resolver}; use swc_typescript::fast_dts::{FastDts, FastDtsOptions}; @@ -22,16 +22,23 @@ fn bench_isolated_declarations(criterion: &mut Criterion) { let unresolved_mark = Mark::new(); let top_level_mark = Mark::new(); let comments = SingleThreadedComments::default(); - let mut program = parse_file_as_program( - &fm, - Syntax::Typescript(Default::default()), - Default::default(), - Some(&comments), - &mut Vec::new(), - ) - .map(|program| program.apply(resolver(unresolved_mark, top_level_mark, true))) - .map(|program| program.apply(paren_remover(None))) - .unwrap(); + let mut result = Parser::new(&fm.src, SourceType::typescript()) + .with_start_pos(fm.start_pos) + .with_tokens() + .parse(); + attach_comments( + &fm.src, + fm.start_pos, + &comments, + std::mem::take(&mut result.comments), + &result.tokens, + &result.program, + ); + assert!(result.diagnostics.is_empty()); + let mut program = result + .program + .apply(resolver(unresolved_mark, top_level_mark, true)) + .apply(paren_remover(None)); let internal_annotations = FastDts::get_internal_annotations(&comments); let mut checker = FastDts::new( diff --git a/crates/swc/examples/hygiene_visualizer.rs b/crates/swc/examples/hygiene_visualizer.rs index 032f1a9961de..eacef898f1ad 100644 --- a/crates/swc/examples/hygiene_visualizer.rs +++ b/crates/swc/examples/hygiene_visualizer.rs @@ -43,7 +43,7 @@ impl Fold for HygieneVisualizer { fn main() { use std::{env, path::Path, process}; - use swc_ecma_parser::{parse_file_as_program, EsSyntax, Syntax, TsSyntax}; + use swc_ecma_parser::{EsSyntax, ModuleKind, Parser, SourceType, Syntax, TsSyntax}; // Get command-line arguments let args: Vec = env::args().collect(); @@ -87,17 +87,20 @@ fn main() { GLOBALS.set(&Default::default(), || { // Parse the file - let mut program = parse_file_as_program( - &source_file, - syntax, - Default::default(), - None, - &mut Vec::new(), - ) - .unwrap_or_else(|e| { - eprintln!("Failed to parse file '{file_path}': {e:?}"); + let (source_type, options) = + SourceType::from_legacy(syntax, ModuleKind::Unambiguous, Default::default()); + let result = Parser::new(&source_file.src, source_type) + .with_options(options) + .with_start_pos(source_file.start_pos) + .parse(); + if !result.diagnostics.is_empty() { + eprintln!( + "Failed to parse file '{file_path}': {:?}", + result.diagnostics + ); process::exit(1) - }); + } + let mut program = result.program; // Apply resolver let unresolved_mark = Mark::new(); diff --git a/crates/swc/examples/node_counter.rs b/crates/swc/examples/node_counter.rs index 4210b2709fc9..e4c280a16467 100644 --- a/crates/swc/examples/node_counter.rs +++ b/crates/swc/examples/node_counter.rs @@ -15,7 +15,7 @@ use std::{ use swc_common::{sync::Lrc, SourceMap, GLOBALS}; use swc_ecma_ast::{Pass, Program}; -use swc_ecma_parser::{parse_file_as_program, EsSyntax, Syntax, TsSyntax}; +use swc_ecma_parser::{EsSyntax, ModuleKind, Parser, SourceType, Syntax, TsSyntax}; use swc_ecma_visit::NodeRef; // Keep this list in sync with generated `swc_ecma_visit::NodeRef`. The @@ -386,17 +386,27 @@ fn main() { let syntax = syntax_for_path(&file_path); GLOBALS.set(&Default::default(), || { - let mut errors = Vec::new(); - let mut program = - parse_file_as_program(&source_file, syntax, Default::default(), None, &mut errors) - .unwrap_or_else(|err| { - eprintln!("Failed to parse '{}': {err:?}", file_path.display()); - process::exit(1); - }); - - if !errors.is_empty() { - eprintln!("Parsed with {} recoverable error(s).", errors.len()); + let (source_type, options) = + SourceType::from_legacy(syntax, ModuleKind::Unambiguous, Default::default()); + let result = Parser::new(&source_file.src, source_type) + .with_options(options) + .with_start_pos(source_file.start_pos) + .parse(); + if result.panicked { + eprintln!( + "Failed to parse '{}': {:?}", + file_path.display(), + result.diagnostics + ); + process::exit(1); + } + if !result.diagnostics.is_empty() { + eprintln!( + "Parsed with {} recoverable error(s).", + result.diagnostics.len() + ); } + let mut program = result.program; let mut counter = NodeCounter::default(); counter.process(&mut program); diff --git a/crates/swc/src/config/mod.rs b/crates/swc/src/config/mod.rs index 15762dbe8f1b..1df9649ec613 100644 --- a/crates/swc/src/config/mod.rs +++ b/crates/swc/src/config/mod.rs @@ -24,10 +24,10 @@ use swc_common::plugin::metadata::TransformPluginMetadataContext; use swc_common::{ comments::{Comments, SingleThreadedComments}, errors::Handler, - FileName, Mark, SourceMap, + BytePos, FileName, Mark, SourceMap, }; #[cfg(feature = "react-compiler")] -use swc_common::{BytePos, Span, Spanned}; +use swc_common::{Span, Spanned}; pub use swc_compiler_base::SourceMapsConfig; pub use swc_config::is_module::IsModule; use swc_config::{ @@ -48,7 +48,7 @@ use swc_ecma_loader::resolvers::{ }; pub use swc_ecma_minifier::js::*; use swc_ecma_minifier::option::terser::TerserTopLevelOptions; -use swc_ecma_parser::{parse_file_as_expr, Syntax, TsSyntax}; +use swc_ecma_parser::{Parser, SourceType, Syntax, TsSyntax}; use swc_ecma_preset_env::{Caniuse, Feature}; pub use swc_ecma_transforms::proposals::DecoratorVersion; use swc_ecma_transforms::{ @@ -2178,24 +2178,23 @@ impl GlobalPassOption { fn expr(cm: &SourceMap, handler: &Handler, src: String) -> Box { let fm = cm.new_source_file(FileName::Anon.into(), src); - - let mut errors = Vec::new(); - let expr = parse_file_as_expr( - &fm, - Syntax::Es(Default::default()), - Default::default(), - None, - &mut errors, - ); - - for e in errors { - e.into_diagnostic(handler).emit() - } - - match expr { - Ok(v) => v, - _ => panic!("{} is not a valid expression", fm.src), + let wrapped = format!("({})", fm.src); + let result = Parser::new(&wrapped, SourceType::script()) + .with_start_pos(BytePos(fm.start_pos.0.saturating_sub(1))) + .parse(); + for error in result.diagnostics { + error.into_diagnostic(handler).emit(); } + let Program::Script(mut script) = result.program else { + unreachable!("script source type must produce a script") + }; + let Some(swc_ecma_ast::Stmt::Expr(statement)) = script.body.pop() else { + panic!("{} is not a valid expression", fm.src); + }; + let Expr::Paren(parenthesized) = *statement.expr else { + unreachable!("wrapped global expression must remain parenthesized") + }; + parenthesized.expr } fn mk_map( diff --git a/crates/swc/src/lib.rs b/crates/swc/src/lib.rs index 90f1c3ff6dd2..1bf637c7c893 100644 --- a/crates/swc/src/lib.rs +++ b/crates/swc/src/lib.rs @@ -149,7 +149,7 @@ use swc_ecma_loader::resolvers::{ }; use swc_ecma_minifier::option::{MangleCache, MinifyOptions, TopLevelOptions}; use swc_ecma_parser::{ - error::SyntaxError, parse_file_as_program, parse_file_as_script, EsSyntax, Syntax, + attach_comments, error::SyntaxError, EsSyntax, ModuleKind, Parser, SourceType, Syntax, }; use swc_ecma_transforms::{ fixer, @@ -648,28 +648,31 @@ impl Compiler { } if matches!(is_module, IsModule::Bool(false)) { - let mut errors = Vec::new(); - match parse_file_as_script(&fm, syntax, target, comments, &mut errors) { - Ok(script) => { - emit_parser_recoverable_errors(handler, errors)?; - return Ok((Program::Script(script), false)); - } - Err(err) if matches!(err.kind(), SyntaxError::ImportExportInScript) => {} - Err(err) => { - emit_parser_recoverable_errors(handler, errors)?; - err.into_diagnostic(handler).emit(); - return Err(Error::msg("Syntax Error")); - } + let (source_type, options) = + SourceType::from_legacy(syntax, ModuleKind::Unambiguous, target); + let parser = Parser::new(&fm.src, source_type) + .with_options(options) + .with_start_pos(fm.start_pos); + let mut result = if comments.is_some() { + parser.with_tokens().parse() + } else { + parser.parse() + }; + if let Some(comments) = comments { + attach_comments( + &fm.src, + fm.start_pos, + comments, + std::mem::take(&mut result.comments), + &result.tokens, + &result.program, + ); } - - let mut errors = Vec::new(); - let program = parse_file_as_program(&fm, syntax, target, comments, &mut errors) - .map_err(|err| { - err.into_diagnostic(handler).emit(); - Error::msg("Syntax Error") - })?; - - emit_parser_recoverable_errors(handler, errors)?; + emit_parser_recoverable_errors(handler, result.diagnostics)?; + if result.panicked { + return Err(Error::msg("Syntax Error")); + } + let program = result.program; match classify_flow_script_like_module(&program) { FlowScriptLikeModuleKind::Script => Ok((program, false)), diff --git a/crates/swc/tests/flow_strip/mod.rs b/crates/swc/tests/flow_strip/mod.rs index 8c713fde8e2a..b2c12f82ff42 100644 --- a/crates/swc/tests/flow_strip/mod.rs +++ b/crates/swc/tests/flow_strip/mod.rs @@ -6,7 +6,7 @@ use swc::{ }; use swc_common::{errors::Handler, sync::Lrc, FileName, SourceMap}; use swc_ecma_ast::EsVersion; -use swc_ecma_parser::{parse_file_as_program, EsSyntax, FlowSyntax, Syntax}; +use swc_ecma_parser::{EsSyntax, FlowSyntax, ModuleKind, Parser, SourceType, Syntax}; use swc_ecma_transforms::react::{Options as ReactOptions, Runtime as ReactRuntime}; pub(crate) fn compile_and_reparse_flow_file( @@ -36,27 +36,19 @@ pub(crate) fn compile_and_reparse_flow_file( ); let output_fm = cm.new_source_file(FileName::Anon.into(), output.code); - let mut recovered_errors = Vec::new(); - let reparse_result = parse_file_as_program( - &output_fm, + let (source_type, options) = SourceType::from_legacy( Syntax::Es(es_reparse_syntax(flow_syntax.jsx)), + ModuleKind::Unambiguous, EsVersion::latest(), - None, - &mut recovered_errors, ); - - if let Err(err) = reparse_result { - err.into_diagnostic(handler).emit(); - for recovered in recovered_errors { - recovered.into_diagnostic(handler).emit(); - } - return Err(()); + let result = Parser::new(&output_fm.src, source_type) + .with_options(options) + .with_start_pos(output_fm.start_pos) + .parse(); + for error in result.diagnostics { + error.into_diagnostic(handler).emit(); } - - if !recovered_errors.is_empty() { - for recovered in recovered_errors { - recovered.into_diagnostic(handler).emit(); - } + if handler.has_errors() { return Err(()); } diff --git a/crates/swc/tests/flow_strip_correctness.rs b/crates/swc/tests/flow_strip_correctness.rs index 9765f27f21cd..0725b07a27bb 100644 --- a/crates/swc/tests/flow_strip_correctness.rs +++ b/crates/swc/tests/flow_strip_correctness.rs @@ -8,7 +8,7 @@ use swc::{ }; use swc_common::FileName; use swc_ecma_ast::EsVersion; -use swc_ecma_parser::{parse_file_as_program, EsSyntax, FlowSyntax, Syntax}; +use swc_ecma_parser::{EsSyntax, FlowSyntax, ModuleKind, Parser, SourceType, Syntax}; use testing::Tester; #[testing::fixture("../swc_ecma_parser/tests/flow/**/*.js")] @@ -53,42 +53,27 @@ fn flow_strip_correctness(input: PathBuf) { ); let output_fm = cm.new_source_file(FileName::Anon.into(), output.code); - let mut recovered_errors = vec![]; - - match parse_file_as_program( - &output_fm, - Syntax::Es(EsSyntax { - jsx: flow_syntax.jsx, - decorators: true, - decorators_before_export: true, - export_default_from: true, - import_attributes: true, - allow_super_outside_method: true, - auto_accessors: true, - explicit_resource_management: true, - ..Default::default() - }), - EsVersion::latest(), - None, - &mut recovered_errors, - ) { - Ok(_) => {} - Err(err) => { - err.into_diagnostic(&handler).emit(); - - for recovered in recovered_errors { - recovered.into_diagnostic(&handler).emit(); - } - - return Err(()); - } + let syntax = Syntax::Es(EsSyntax { + jsx: flow_syntax.jsx, + decorators: true, + decorators_before_export: true, + export_default_from: true, + import_attributes: true, + allow_super_outside_method: true, + auto_accessors: true, + explicit_resource_management: true, + ..Default::default() + }); + let (source_type, options) = + SourceType::from_legacy(syntax, ModuleKind::Unambiguous, EsVersion::latest()); + let result = Parser::new(&output_fm.src, source_type) + .with_options(options) + .with_start_pos(output_fm.start_pos) + .parse(); + for error in result.diagnostics { + error.into_diagnostic(&handler).emit(); } - - if !recovered_errors.is_empty() { - for recovered in recovered_errors { - recovered.into_diagnostic(&handler).emit(); - } - + if handler.has_errors() { return Err(()); } diff --git a/crates/swc/tests/serde.rs b/crates/swc/tests/serde.rs index bb8715fd6148..f4081a03acea 100644 --- a/crates/swc/tests/serde.rs +++ b/crates/swc/tests/serde.rs @@ -1,78 +1,62 @@ use std::path::Path; -use swc_ecma_ast::Module; -use swc_ecma_parser::{lexer::Lexer, PResult, Parser, Syntax}; +use swc_ecma_ast::{Module, Program}; +use swc_ecma_parser::{Parser, SourceType}; use testing::NormalizedOutput; -fn with_parser(file_name: &str, f: F) -> Result +fn with_module(file_name: &str, f: F) -> Result where - F: FnOnce(&mut Parser) -> PResult, + F: FnOnce(Module) -> Ret, { ::testing::run_test(false, |cm, handler| { let fm = cm .load_file(Path::new(file_name)) .unwrap_or_else(|e| panic!("failed to load {file_name}: {e}")); - let lexer = Lexer::new( - if file_name.ends_with(".ts") { - Syntax::Typescript(Default::default()) - } else { - Syntax::default() - }, - Default::default(), - (&*fm).into(), - None, - ); - let mut p = Parser::new_from(lexer); - let res = f(&mut p).map_err(|e| e.into_diagnostic(handler).emit()); - - for e in p.take_errors() { - e.into_diagnostic(handler).emit() + let source_type = if file_name.ends_with(".ts") { + SourceType::typescript() + } else { + SourceType::module() + }; + let result = Parser::new(&fm.src, source_type) + .with_start_pos(fm.start_pos) + .parse(); + for error in result.diagnostics { + error.into_diagnostic(handler).emit(); } - if handler.has_errors() { return Err(()); } - - res + let Program::Module(module) = result.program else { + unreachable!("module source type must produce a module") + }; + Ok(f(module)) }) } #[test] fn color_js() { - with_parser("tests/serde/colors.js", |p| { - let m = p.parse_module()?; - + with_module("tests/serde/colors.js", |m| { let s = serde_json::to_string(&m).expect("failed to serialize"); let _: Module = serde_json::from_str(&s).expect("failed to deserialize"); - - Ok(()) }) .expect("failed"); } #[test] fn color_ts() { - with_parser("tests/serde/colors.ts", |p| { - let m = p.parse_module()?; - + with_module("tests/serde/colors.ts", |m| { let s = serde_json::to_string(&m).expect("failed to serialize"); let _: Module = serde_json::from_str(&s).expect("failed to deserialize"); - - Ok(()) }) .expect("failed"); } #[test] fn super_js() { - with_parser("tests/serde/supers.js", |p| { - let m = p.parse_module()?; - + with_module("tests/serde/supers.js", |m| { let s = serde_json::to_string(&m).expect("failed to serialize"); let _: Module = serde_json::from_str(&s).expect("failed to deserialize"); - - Ok(()) }) .expect("failed"); } diff --git a/crates/swc/tests/tsc-references/parserArrowFunctionExpression10.1.normal.js b/crates/swc/tests/tsc-references/parserArrowFunctionExpression10.1.normal.js index acfdbdc73963..496e22bf8e9f 100644 --- a/crates/swc/tests/tsc-references/parserArrowFunctionExpression10.1.normal.js +++ b/crates/swc/tests/tsc-references/parserArrowFunctionExpression10.1.normal.js @@ -1,6 +1,5 @@ //// [parserArrowFunctionExpression10.ts] //// [fileJs.js] a ? (b)=>d : (e)=>f; // Not legal JS; "Unexpected token ':'" at last colon - // Not legal JS; "Unexpected token ':'" at last colon //// [fileTs.ts] a ? (b)=>d : (e)=>f; diff --git a/crates/swc/tests/tsc-references/parserArrowFunctionExpression12.1.normal.js b/crates/swc/tests/tsc-references/parserArrowFunctionExpression12.1.normal.js index f2e15071639b..d978b19d678c 100644 --- a/crates/swc/tests/tsc-references/parserArrowFunctionExpression12.1.normal.js +++ b/crates/swc/tests/tsc-references/parserArrowFunctionExpression12.1.normal.js @@ -1,6 +1,5 @@ //// [parserArrowFunctionExpression12.ts] //// [fileJs.js] a ? (b)=>c : (d)=>e; // Legal JS - // Legal JS //// [fileTs.ts] a ? (b)=>c : (d)=>e; diff --git a/crates/swc/tests/tsc-references/parserArrowFunctionExpression8.1.normal.js b/crates/swc/tests/tsc-references/parserArrowFunctionExpression8.1.normal.js index 1830b5928d32..43fde223c46e 100644 --- a/crates/swc/tests/tsc-references/parserArrowFunctionExpression8.1.normal.js +++ b/crates/swc/tests/tsc-references/parserArrowFunctionExpression8.1.normal.js @@ -5,7 +5,6 @@ x ? (y)=>({ }) : (z)=>({ z } // Legal JS - // Legal JS ); //// [fileTs.ts] x ? (y)=>({ diff --git a/crates/swc/tests/tsc-references/parserArrowFunctionExpression9.1.normal.js b/crates/swc/tests/tsc-references/parserArrowFunctionExpression9.1.normal.js index a84a1f4a9236..425a989141c5 100644 --- a/crates/swc/tests/tsc-references/parserArrowFunctionExpression9.1.normal.js +++ b/crates/swc/tests/tsc-references/parserArrowFunctionExpression9.1.normal.js @@ -1,6 +1,5 @@ //// [parserArrowFunctionExpression9.ts] //// [fileJs.js] b ? c : (d)=>e; // Legal JS - // Legal JS //// [fileTs.ts] b ? c : (d)=>e; diff --git a/crates/swc_bundler/examples/bundle.rs b/crates/swc_bundler/examples/bundle.rs index 6b823175da08..c9c9efb1a981 100644 --- a/crates/swc_bundler/examples/bundle.rs +++ b/crates/swc_bundler/examples/bundle.rs @@ -30,7 +30,7 @@ use swc_ecma_loader::{ use swc_ecma_minifier::option::{ CompressOptions, ExtraOptions, MangleOptions, MinifyOptions, TopLevelOptions, }; -use swc_ecma_parser::{parse_file_as_module, Syntax}; +use swc_ecma_parser::{ModuleKind, Parser, SourceType, Syntax}; use swc_ecma_transforms_base::fixer::fixer; use swc_ecma_visit::VisitMutWith; @@ -227,19 +227,26 @@ impl Load for Loader { _ => unreachable!(), }; - let module = parse_file_as_module( - &fm, + let (source_type, options) = SourceType::from_legacy( Syntax::Es(Default::default()), + ModuleKind::Module, EsVersion::Es2020, - None, - &mut Vec::new(), - ) - .unwrap_or_else(|err| { + ); + let result = Parser::new(&fm.src, source_type) + .with_options(options) + .with_start_pos(fm.start_pos) + .parse(); + if !result.diagnostics.is_empty() { let handler = Handler::with_tty_emitter(ColorConfig::Always, false, false, Some(self.cm.clone())); - err.into_diagnostic(&handler).emit(); + for error in result.diagnostics { + error.into_diagnostic(&handler).emit(); + } panic!("failed to parse") - }); + } + let Program::Module(module) = result.program else { + unreachable!("module source type must produce a module") + }; Ok(ModuleData { fm, diff --git a/crates/swc_bundler/examples/path.rs b/crates/swc_bundler/examples/path.rs index 3eb0f7535461..79301e4e42aa 100644 --- a/crates/swc_bundler/examples/path.rs +++ b/crates/swc_bundler/examples/path.rs @@ -3,10 +3,10 @@ use std::{collections::HashMap, io::stdout}; use anyhow::Error; use swc_bundler::{BundleKind, Bundler, Config, Hook, Load, ModuleData, ModuleRecord, Resolve}; use swc_common::{sync::Lrc, FileName, FilePathMapping, Globals, SourceMap, Span}; -use swc_ecma_ast::KeyValueProp; +use swc_ecma_ast::{KeyValueProp, Program}; use swc_ecma_codegen::{text_writer::JsWriter, Emitter}; use swc_ecma_loader::resolve::Resolution; -use swc_ecma_parser::{parse_file_as_module, Syntax}; +use swc_ecma_parser::{Parser, SourceType}; fn main() { let _log = testing::init(); @@ -69,14 +69,13 @@ impl Load for PathLoader { let fm = self.cm.load_file(file)?; - let module = parse_file_as_module( - &fm, - Syntax::Es(Default::default()), - Default::default(), - None, - &mut Vec::new(), - ) - .expect("This should not happen"); + let result = Parser::new(&fm.src, SourceType::module()) + .with_start_pos(fm.start_pos) + .parse(); + assert!(result.diagnostics.is_empty(), "This should not happen"); + let Program::Module(module) = result.program else { + unreachable!("module source type must produce a module") + }; Ok(ModuleData { fm, diff --git a/crates/swc_bundler/src/bundler/helpers/mod.rs b/crates/swc_bundler/src/bundler/helpers/mod.rs index d99629fae243..3654cec1d195 100644 --- a/crates/swc_bundler/src/bundler/helpers/mod.rs +++ b/crates/swc_bundler/src/bundler/helpers/mod.rs @@ -1,9 +1,8 @@ use std::sync::atomic::{AtomicBool, Ordering::SeqCst}; use once_cell::sync::Lazy; -use swc_common::{FileName, FilePathMapping, SourceMap}; use swc_ecma_ast::*; -use swc_ecma_parser::parse_file_as_module; +use swc_ecma_parser::{Parser, SourceType}; use swc_ecma_utils::{drop_span, prepend_stmts}; #[derive(Debug, Default)] @@ -13,18 +12,16 @@ pub(crate) struct Helpers { } fn parse(code: &'static str, name: &'static str) -> Vec { - let cm = SourceMap::new(FilePathMapping::empty()); - let fm = cm.new_source_file(FileName::Custom(name.into()).into(), code); - parse_file_as_module( - &fm, - Default::default(), - Default::default(), - None, - &mut Vec::new(), - ) - .map(|script| drop_span(script.body)) - .map_err(|_| {}) - .unwrap() + let parsed = Parser::new(code, SourceType::module()).parse(); + assert!( + !parsed.panicked, + "failed to parse bundler helper {name}: {:?}", + parsed.diagnostics + ); + match parsed.program { + Program::Module(module) => drop_span(module.body), + Program::Script(_) => unreachable!("module source type produced a script"), + } } macro_rules! define { diff --git a/crates/swc_bundler/src/bundler/tests.rs b/crates/swc_bundler/src/bundler/tests.rs index 7fe8e7250470..35534ae95c6d 100644 --- a/crates/swc_bundler/src/bundler/tests.rs +++ b/crates/swc_bundler/src/bundler/tests.rs @@ -7,7 +7,7 @@ use rustc_hash::FxBuildHasher; use swc_common::{sync::Lrc, FileName, SourceMap, Span, GLOBALS}; use swc_ecma_ast::*; use swc_ecma_loader::resolve::Resolution; -use swc_ecma_parser::{lexer::Lexer, Parser, StringInput}; +use swc_ecma_parser::{Parser, SourceType}; use swc_ecma_utils::drop_span; use swc_ecma_visit::VisitMutWith; @@ -32,15 +32,13 @@ impl Load for Loader { let fm = self.cm.new_source_file(f.clone().into(), v.to_string()); - let lexer = Lexer::new( - Default::default(), - EsVersion::Es2020, - StringInput::from(&*fm), - None, - ); - - let mut parser = Parser::new_from(lexer); - let module = parser.parse_module().unwrap(); + let result = Parser::new(&fm.src, SourceType::module()) + .with_start_pos(fm.start_pos) + .parse(); + assert!(result.diagnostics.is_empty()); + let Program::Module(module) = result.program else { + unreachable!("module source type must produce a module") + }; Ok(ModuleData { fm, @@ -85,14 +83,14 @@ impl Tester<'_> { s.to_string(), ); - let lexer = Lexer::new( - Default::default(), - Default::default(), - StringInput::from(&*fm), - None, - ); - let mut parser = Parser::new_from(lexer); - parser.parse_module().unwrap() + let result = Parser::new(&fm.src, SourceType::module()) + .with_start_pos(fm.start_pos) + .parse(); + assert!(result.diagnostics.is_empty()); + let Program::Module(module) = result.program else { + unreachable!("module source type must produce a module") + }; + module } #[allow(dead_code)] diff --git a/crates/swc_bundler/src/util.rs b/crates/swc_bundler/src/util.rs index 63b136f1d9a1..1f9561b513c0 100644 --- a/crates/swc_bundler/src/util.rs +++ b/crates/swc_bundler/src/util.rs @@ -2,6 +2,7 @@ use std::hash::Hash; +#[cfg(feature = "concurrent")] use rustc_hash::FxBuildHasher; #[cfg(not(feature = "concurrent"))] use rustc_hash::FxHashMap; diff --git a/crates/swc_bundler/tests/common/mod.rs b/crates/swc_bundler/tests/common/mod.rs index ac279658bab3..50a06770c801 100644 --- a/crates/swc_bundler/tests/common/mod.rs +++ b/crates/swc_bundler/tests/common/mod.rs @@ -18,7 +18,7 @@ use swc_common::{ }; use swc_ecma_ast::{EsVersion, Program}; use swc_ecma_loader::resolve::Resolution; -use swc_ecma_parser::{parse_file_as_module, Syntax, TsSyntax}; +use swc_ecma_parser::{ModuleKind, Parser, SourceType, Syntax, TsSyntax}; use swc_ecma_transforms_base::{ helpers::{inject_helpers, Helpers, HELPERS}, resolver, @@ -114,23 +114,28 @@ impl Load for Loader { _ => unreachable!(), }; - let module = parse_file_as_module( - &fm, - Syntax::Typescript(TsSyntax { - decorators: true, - tsx, - ..Default::default() - }), - EsVersion::Es2020, - None, - &mut Vec::new(), - ) - .unwrap_or_else(|err| { + let syntax = Syntax::Typescript(TsSyntax { + decorators: true, + tsx, + ..Default::default() + }); + let (source_type, options) = + SourceType::from_legacy(syntax, ModuleKind::Module, EsVersion::Es2020); + let result = Parser::new(&fm.src, source_type) + .with_options(options) + .with_start_pos(fm.start_pos) + .parse(); + if !result.diagnostics.is_empty() { let handler = Handler::with_tty_emitter(ColorConfig::Always, false, false, Some(self.cm.clone())); - err.into_diagnostic(&handler).emit(); + for error in result.diagnostics { + error.into_diagnostic(&handler).emit(); + } panic!("failed to parse") - }); + } + let Program::Module(module) = result.program else { + unreachable!("module source type must produce a module") + }; let module = HELPERS.set(&Helpers::new(false), || { Program::Module(module) diff --git a/crates/swc_compiler_base/src/lib.rs b/crates/swc_compiler_base/src/lib.rs index 632385b50bec..df55cb74321d 100644 --- a/crates/swc_compiler_base/src/lib.rs +++ b/crates/swc_compiler_base/src/lib.rs @@ -25,10 +25,7 @@ use swc_ecma_codegen::{ Emitter, Node, }; use swc_ecma_minifier::js::JsMinifyCommentOption; -use swc_ecma_parser::{ - parse_file_as_commonjs, parse_file_as_module, parse_file_as_program, parse_file_as_script, - Syntax, -}; +use swc_ecma_parser::{attach_comments, ModuleKind, Parser, SourceType, Syntax}; use swc_ecma_visit::{noop_visit_type, Visit, VisitWith}; mod source_map_scopes; @@ -82,38 +79,42 @@ pub fn parse_js( let mut res = (|| { let mut error = false; - let mut errors = std::vec::Vec::new(); - let program_result = match is_module { - IsModule::Bool(true) => { - parse_file_as_module(&fm, syntax, target, comments, &mut errors) - .map(Program::Module) - } - IsModule::Bool(false) => { - parse_file_as_script(&fm, syntax, target, comments, &mut errors) - .map(Program::Script) - } - IsModule::CommonJS => { - parse_file_as_commonjs(&fm, syntax, target, comments, &mut errors) - .map(Program::Script) - } - IsModule::Unknown => parse_file_as_program(&fm, syntax, target, comments, &mut errors), + let module_kind = match is_module { + IsModule::Bool(true) => ModuleKind::Module, + IsModule::Bool(false) => ModuleKind::Script, + IsModule::CommonJS => ModuleKind::CommonJs, + IsModule::Unknown => ModuleKind::Unambiguous, + }; + let (source_type, options) = SourceType::from_legacy(syntax, module_kind, target); + let parser = Parser::new(&fm.src, source_type) + .with_options(options) + .with_start_pos(fm.start_pos); + let mut result = if comments.is_some() { + parser.with_tokens().parse() + } else { + parser.parse() }; + if let Some(comments) = comments { + attach_comments( + &fm.src, + fm.start_pos, + comments, + std::mem::take(&mut result.comments), + &result.tokens, + &result.program, + ); + } - for e in errors { + for e in result.diagnostics { e.into_diagnostic(handler).emit(); error = true; } - let program = program_result.map_err(|e| { - e.into_diagnostic(handler).emit(); - Error::msg("Syntax Error") - })?; - - if error { + if error || result.panicked { return Err(anyhow::anyhow!("Syntax Error")); } - Ok(program) + Ok(result.program) })(); if env::var("SWC_DEBUG").unwrap_or_default() == "1" { diff --git a/crates/swc_compiler_base/tests/source_map_scopes.rs b/crates/swc_compiler_base/tests/source_map_scopes.rs index 0388bc85ee4a..b1306d39a788 100644 --- a/crates/swc_compiler_base/tests/source_map_scopes.rs +++ b/crates/swc_compiler_base/tests/source_map_scopes.rs @@ -4,19 +4,16 @@ use swc_common::{sync::Lrc, FileName, FilePathMapping, SourceMap}; use swc_compiler_base::{print, IdentCollector, PrintArgs, SourceMapsConfig}; use swc_ecma_ast::{EsVersion, Program}; use swc_ecma_codegen::Config as CodegenConfig; -use swc_ecma_parser::{lexer::Lexer, EsSyntax, Parser, Syntax}; +use swc_ecma_parser::{Parser, SourceType}; use swc_ecma_visit::VisitWith; fn parse_program(cm: Lrc, src: &str) -> Program { let fm = cm.new_source_file(FileName::Real("input.js".into()).into(), src.to_owned()); - let lexer = Lexer::new( - Syntax::Es(EsSyntax::default()), - EsVersion::Es2022, - (&*fm).into(), - None, - ); - let mut parser = Parser::new_from(lexer); - Program::Module(parser.parse_module().expect("module should parse")) + let result = Parser::new(&fm.src, SourceType::module()) + .with_start_pos(fm.start_pos) + .parse(); + assert!(result.diagnostics.is_empty(), "module should parse"); + result.program } fn print_map( diff --git a/crates/swc_core/Cargo.toml b/crates/swc_core/Cargo.toml index 4f5ead0be4c8..d34a9a6ff1c3 100644 --- a/crates/swc_core/Cargo.toml +++ b/crates/swc_core/Cargo.toml @@ -149,7 +149,6 @@ ecma_ast_shrink = ["ecma_ast", "swc_ecma_ast/shrink-to-fit"] # Enable swc_ecma_parser support. ecma_parser = ["__parser"] ecma_parser_typescript = ["__parser", "swc_ecma_parser/typescript"] -ecma_parser_unstable = ["__parser", "swc_ecma_parser/unstable"] # Enable swc_ecma_codegen ecma_codegen = ["__ecma", "swc_ecma_codegen"] diff --git a/crates/swc_ecma_codegen/benches/bench.rs b/crates/swc_ecma_codegen/benches/bench.rs index f64e405fdadc..dfc7d05c31ea 100644 --- a/crates/swc_ecma_codegen/benches/bench.rs +++ b/crates/swc_ecma_codegen/benches/bench.rs @@ -2,8 +2,9 @@ extern crate swc_malloc; use codspeed_criterion_compat::{black_box, criterion_group, criterion_main, Bencher, Criterion}; use swc_common::{source_map::DefaultSourceMapGenConfig, FileName}; +use swc_ecma_ast::Program; use swc_ecma_codegen::Emitter; -use swc_ecma_parser::{Parser, StringInput, Syntax}; +use swc_ecma_parser::{Parser, SourceType}; const COLORS_JS: &str = r#" 'use strict'; @@ -82,16 +83,15 @@ const LARGE_PARTIAL_JS: &str = include_str!("large-partial.js"); fn bench_emitter(b: &mut Bencher, s: &'static str) { let _ = ::testing::run_test(true, |cm, handler| { let fm = cm.new_source_file(FileName::Anon.into(), s); - let mut parser = Parser::new(Syntax::default(), StringInput::from(&*fm), None); - - let module = parser - .parse_module() - .map_err(|e| e.into_diagnostic(handler).emit()) - .unwrap(); - - for err in parser.take_errors() { - err.into_diagnostic(handler).emit(); + let result = Parser::new(&fm.src, SourceType::module()) + .with_start_pos(fm.start_pos) + .parse(); + for error in result.diagnostics { + error.into_diagnostic(handler).emit(); } + let Program::Module(module) = result.program else { + unreachable!("module source type must produce a module") + }; b.iter(|| { let mut src_map_buf = Vec::new(); diff --git a/crates/swc_ecma_codegen/benches/with_parse.rs b/crates/swc_ecma_codegen/benches/with_parse.rs index bcd327c7cb27..1395d069ee2d 100644 --- a/crates/swc_ecma_codegen/benches/with_parse.rs +++ b/crates/swc_ecma_codegen/benches/with_parse.rs @@ -2,8 +2,9 @@ extern crate swc_malloc; use codspeed_criterion_compat::{black_box, criterion_group, criterion_main, Bencher, Criterion}; use swc_common::{source_map::DefaultSourceMapGenConfig, FileName}; +use swc_ecma_ast::Program; use swc_ecma_codegen::Emitter; -use swc_ecma_parser::{Parser, StringInput, Syntax}; +use swc_ecma_parser::{Parser, SourceType}; const COLORS_JS: &str = r#" 'use strict'; @@ -83,15 +84,15 @@ fn bench_emitter(b: &mut Bencher, s: &'static str) { let _ = ::testing::run_test(true, |cm, handler| { b.iter(|| { let fm = cm.new_source_file(FileName::Anon.into(), s); - let mut parser = Parser::new(Syntax::default(), StringInput::from(&*fm), None); - let module = parser - .parse_module() - .map_err(|e| e.into_diagnostic(handler).emit()) - .unwrap(); - - for err in parser.take_errors() { - err.into_diagnostic(handler).emit(); + let result = Parser::new(&fm.src, SourceType::module()) + .with_start_pos(fm.start_pos) + .parse(); + for error in result.diagnostics { + error.into_diagnostic(handler).emit(); } + let Program::Module(module) = result.program else { + unreachable!("module source type must produce a module") + }; let mut src_map_buf = Vec::new(); let mut buf = Vec::new(); diff --git a/crates/swc_ecma_codegen/examples/gen.rs b/crates/swc_ecma_codegen/examples/gen.rs index 76ec14e58792..1cce6c5a074e 100644 --- a/crates/swc_ecma_codegen/examples/gen.rs +++ b/crates/swc_ecma_codegen/examples/gen.rs @@ -3,25 +3,22 @@ extern crate swc_malloc; use std::{env, fs, path::Path, time::Instant}; -use swc_common::input::SourceFileInput; use swc_ecma_ast::*; use swc_ecma_codegen::{text_writer::JsWriter, Emitter}; -use swc_ecma_parser::{lexer::Lexer, Parser, Syntax}; +use swc_ecma_parser::{ModuleKind, Parser, SourceType}; fn parse_and_gen(entry: &Path) { testing::run_test2(false, |cm, _| { let fm = cm.load_file(entry).unwrap(); - let lexer = Lexer::new( - Syntax::Typescript(Default::default()), - EsVersion::latest(), - SourceFileInput::from(&*fm), - None, - ); - let mut parser = Parser::new_from(lexer); - let m = parser - .parse_module() - .expect("failed to parse input as a module"); + let source_type = SourceType::typescript().with_module_kind(ModuleKind::Module); + let result = Parser::new(&fm.src, source_type) + .with_start_pos(fm.start_pos) + .parse(); + assert!(result.diagnostics.is_empty()); + let Program::Module(m) = result.program else { + unreachable!("TypeScript source type must produce a module") + }; let code = { let mut buf = Vec::new(); diff --git a/crates/swc_ecma_codegen/examples/parse_print.rs b/crates/swc_ecma_codegen/examples/parse_print.rs index 56576ad9353c..e056a41219bc 100644 --- a/crates/swc_ecma_codegen/examples/parse_print.rs +++ b/crates/swc_ecma_codegen/examples/parse_print.rs @@ -21,7 +21,9 @@ use swc_common::{ }; use swc_ecma_ast::{EsVersion, Module}; use swc_ecma_codegen::{text_writer::JsWriter, Emitter, Node}; -use swc_ecma_parser::{parse_file_as_module, EsSyntax, Syntax, TsSyntax}; +use swc_ecma_parser::{ + attach_comments, EsSyntax, ModuleKind, Parser, SourceType, Syntax, TsSyntax, +}; fn main() { if let Err(err) = run() { @@ -102,17 +104,25 @@ fn syntax_for_path(path: &Path) -> Syntax { } fn parse_module(input: &Input, comments: &SingleThreadedComments) -> Result { - let mut errors = Vec::new(); - let module = parse_file_as_module( - &input.file, - input.syntax, - EsVersion::latest(), - Some(comments as &dyn Comments), - &mut errors, + let (source_type, options) = + SourceType::from_legacy(input.syntax, ModuleKind::Module, EsVersion::latest()); + let mut result = Parser::new(&input.file.src, source_type) + .with_options(options) + .with_start_pos(input.file.start_pos) + .with_tokens() + .parse(); + attach_comments( + &input.file.src, + input.file.start_pos, + comments as &dyn Comments, + std::mem::take(&mut result.comments), + &result.tokens, + &result.program, ); - if !errors.is_empty() { - let messages = errors + if !result.diagnostics.is_empty() { + let messages = result + .diagnostics .into_iter() .map(|err| err.kind().msg()) .collect::>() @@ -120,7 +130,10 @@ fn parse_module(input: &Input, comments: &SingleThreadedComments) -> Result String { let comments = Default::default(); let res = { - let mut parser = Parser::new(syntax, StringInput::from(&*src), Some(&comments)); - let res = parser - .parse_module() - .map_err(|e| e.into_diagnostic(handler).emit()); - - for err in parser.take_errors() { - err.into_diagnostic(handler).emit() + let (source_type, options) = + SourceType::from_legacy(syntax, ModuleKind::Module, cfg.target); + let mut result = Parser::new(&src.src, source_type) + .with_options(options) + .with_start_pos(src.start_pos) + .with_tokens() + .parse(); + attach_comments( + &src.src, + src.start_pos, + &comments, + std::mem::take(&mut result.comments), + &result.tokens, + &result.program, + ); + for error in result.diagnostics { + error.into_diagnostic(handler).emit(); } - - res? + let Program::Module(module) = result.program else { + unreachable!("module source type must produce a module") + }; + module }; let out = Builder { cfg, cm, comments }.text(from, |e| res.emit_with(e).unwrap()); diff --git a/crates/swc_ecma_codegen/tests/fixture.rs b/crates/swc_ecma_codegen/tests/fixture.rs index 8ab674505476..4225b7830b82 100644 --- a/crates/swc_ecma_codegen/tests/fixture.rs +++ b/crates/swc_ecma_codegen/tests/fixture.rs @@ -10,7 +10,7 @@ use swc_ecma_codegen::{ text_writer::{JsWriter, WriteJs}, Emitter, }; -use swc_ecma_parser::{parse_file_as_module, Syntax, TsSyntax}; +use swc_ecma_parser::{attach_comments, ModuleKind, Parser, SourceType, Syntax, TsSyntax}; use testing::{run_test2, NormalizedOutput}; const fn true_by_default() -> bool { @@ -61,19 +61,31 @@ fn run(input: &Path, minify: bool) { let fm = cm.load_file(input).unwrap(); let comments = SingleThreadedComments::default(); - let m = parse_file_as_module( - &fm, - Syntax::Typescript(TsSyntax { - decorators: true, - tsx: true, - dts, - ..Default::default() - }), - EsVersion::latest(), - Some(&comments), - &mut Vec::new(), - ) - .expect("failed to parse input as a module"); + let syntax = Syntax::Typescript(TsSyntax { + decorators: true, + tsx: true, + dts, + ..Default::default() + }); + let (source_type, options) = + SourceType::from_legacy(syntax, ModuleKind::Module, EsVersion::latest()); + let mut result = Parser::new(&fm.src, source_type) + .with_options(options) + .with_start_pos(fm.start_pos) + .with_tokens() + .parse(); + attach_comments( + &fm.src, + fm.start_pos, + &comments, + std::mem::take(&mut result.comments), + &result.tokens, + &result.program, + ); + assert!(result.diagnostics.is_empty(), "failed to parse input"); + let swc_ecma_ast::Program::Module(m) = result.program else { + unreachable!("module source type must produce a module") + }; let mut buf = Vec::new(); diff --git a/crates/swc_ecma_codegen/tests/sourcemap.rs b/crates/swc_ecma_codegen/tests/sourcemap.rs index ae756c65229c..c394d4bdf846 100644 --- a/crates/swc_ecma_codegen/tests/sourcemap.rs +++ b/crates/swc_ecma_codegen/tests/sourcemap.rs @@ -3,10 +3,10 @@ use std::{fs::read_to_string, path::PathBuf}; use base64::prelude::{Engine, BASE64_STANDARD}; use rustc_hash::FxBuildHasher; use swc_allocator::api::global::HashSet; -use swc_common::{comments::SingleThreadedComments, source_map::SourceMapGenConfig}; -use swc_ecma_ast::EsVersion; +use swc_common::source_map::SourceMapGenConfig; +use swc_ecma_ast::{EsVersion, Program}; use swc_ecma_codegen::{text_writer::WriteJs, Emitter}; -use swc_ecma_parser::{lexer::Lexer, Parser, Syntax}; +use swc_ecma_parser::{Parser, SourceType}; use swc_ecma_testing::{exec_node_js, JsExecOptions}; use swc_sourcemap::SourceMap; @@ -306,14 +306,20 @@ fn identity(entry: PathBuf) { println!("Expected code:\n{expected_code}"); let expected_tokens = print_source_map(&expected_map); - let comments = SingleThreadedComments::default(); - let lexer = Lexer::new( - Syntax::default(), - Default::default(), - (&*fm).into(), - Some(&comments), - ); - let mut parser: Parser = Parser::new_from(lexer); + let source_type = if is_module { + SourceType::module() + } else { + SourceType::script() + }; + let result = Parser::new(&fm.src, source_type) + .with_start_pos(fm.start_pos) + .parse(); + for error in result.diagnostics { + error.into_diagnostic(handler).emit(); + } + if handler.has_errors() { + return Err(()); + } let mut src_map = Vec::new(); { @@ -337,22 +343,9 @@ fn identity(entry: PathBuf) { }; // Parse source - if is_module { - emitter - .emit_module( - &parser - .parse_module() - .map_err(|e| e.into_diagnostic(handler).emit())?, - ) - .unwrap(); - } else { - emitter - .emit_script( - &parser - .parse_script() - .map_err(|e| e.into_diagnostic(handler).emit())?, - ) - .unwrap(); + match &result.program { + Program::Module(module) => emitter.emit_module(module).unwrap(), + Program::Script(script) => emitter.emit_script(script).unwrap(), } } diff --git a/crates/swc_ecma_codegen/tests/test262.rs b/crates/swc_ecma_codegen/tests/test262.rs index b754a244681b..e821a5b37518 100644 --- a/crates/swc_ecma_codegen/tests/test262.rs +++ b/crates/swc_ecma_codegen/tests/test262.rs @@ -4,9 +4,9 @@ use std::{ }; use swc_common::comments::SingleThreadedComments; -use swc_ecma_ast::EsVersion; +use swc_ecma_ast::{EsVersion, Program}; use swc_ecma_codegen::{text_writer::WriteJs, Emitter}; -use swc_ecma_parser::{lexer::Lexer, Parser, Syntax}; +use swc_ecma_parser::{attach_comments, Parser, SourceType}; use testing::NormalizedOutput; const IGNORED_PASS_TESTS: &[&str] = &[ @@ -112,13 +112,29 @@ fn do_test(entry: &Path, minify: bool) { ); let comments = SingleThreadedComments::default(); - let lexer = Lexer::new( - Syntax::default(), - Default::default(), - (&*src).into(), - Some(&comments), + let source_type = if module { + SourceType::module() + } else { + SourceType::script() + }; + let mut result = Parser::new(&src.src, source_type) + .with_start_pos(src.start_pos) + .with_tokens() + .parse(); + attach_comments( + &src.src, + src.start_pos, + &comments, + std::mem::take(&mut result.comments), + &result.tokens, + &result.program, ); - let mut parser: Parser = Parser::new_from(lexer); + for error in result.diagnostics { + error.into_diagnostic(handler).emit(); + } + if handler.has_errors() { + return Err(()); + } { let mut wr = Box::new(swc_ecma_codegen::text_writer::JsWriter::new( @@ -142,22 +158,9 @@ fn do_test(entry: &Path, minify: bool) { }; // Parse source - if module { - emitter - .emit_module( - &parser - .parse_module() - .map_err(|e| e.into_diagnostic(handler).emit())?, - ) - .unwrap(); - } else { - emitter - .emit_script( - &parser - .parse_script() - .map_err(|e| e.into_diagnostic(handler).emit())?, - ) - .unwrap(); + match &result.program { + Program::Module(module) => emitter.emit_module(module).unwrap(), + Program::Script(script) => emitter.emit_script(script).unwrap(), } } let ref_file = format!("{}", ref_dir.join(&file_name).display()); diff --git a/crates/swc_ecma_lexer/Cargo.toml b/crates/swc_ecma_lexer/Cargo.toml deleted file mode 100644 index 50e401233770..000000000000 --- a/crates/swc_ecma_lexer/Cargo.toml +++ /dev/null @@ -1,59 +0,0 @@ -[package] -authors = ["강동윤 "] -description = "Feature-complete es2019 parser." -documentation = "https://rustdoc.swc.rs/swc_ecma_lexer/" -edition = { workspace = true } -include = ["Cargo.toml", "src/**/*.rs", "examples/**/*.rs"] -license = { workspace = true } -name = "swc_ecma_lexer" -repository = { workspace = true } -version = "40.0.1" - -[package.metadata.docs.rs] -all-features = true -rustdoc-args = ["--cfg", "docsrs"] - -[lib] -bench = false - -[lints.rust] -unexpected_cfgs = { level = "warn", check-cfg = ['cfg(swc_ast_unknown)'] } - -[features] -# Used for debugging -debug = ["tracing-spans"] -default = ["typescript", "stacker"] -tracing-spans = [] -typescript = [] -verify = ["swc_ecma_visit"] - -[dependencies] -bitflags = { workspace = true } -compact_str = { workspace = true } -either = { workspace = true } -num-bigint = { workspace = true } -rustc-hash = { workspace = true } -seq-macro = { workspace = true } -serde = { workspace = true, features = ["derive"] } -smallvec = { workspace = true } -tracing = { workspace = true } - -swc_atoms = { version = "9.0.3", path = "../swc_atoms" } -swc_common = { version = "23.0.2", path = "../swc_common" } -swc_ecma_ast = { version = "25.0.0", path = "../swc_ecma_ast" } -swc_ecma_parser = { version = "41.1.2", path = "../swc_ecma_parser" } -swc_ecma_visit = { version = "25.0.0", path = "../swc_ecma_visit", optional = true } - -[target.'cfg(not(any(target_arch = "wasm32", target_arch = "arm")))'.dependencies] -stacker = { version = "0.1.15", optional = true } - -[dev-dependencies] - -swc_ecma_ast = { version = "25.0.0", path = "../swc_ecma_ast", features = [ - "serde-impl", -] } -swc_ecma_visit = { version = "25.0.0", path = "../swc_ecma_visit" } -testing = { version = "24.0.1", path = "../testing" } - -[[example]] -name = "lexer" diff --git a/crates/swc_ecma_lexer/README.md b/crates/swc_ecma_lexer/README.md deleted file mode 100644 index 2d31218dde4f..000000000000 --- a/crates/swc_ecma_lexer/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# swc_ecma_lexer - -This crate is no longer maintained and kept just for compatibility. Please use [swc_ecma_parser](https://github.com/swc-project/swc/tree/main/crates/swc_ecma_parser) instead, which provides higher performance. - -See more details in https://github.com/swc-project/swc/pull/11148. diff --git a/crates/swc_ecma_lexer/examples/lexer.rs b/crates/swc_ecma_lexer/examples/lexer.rs deleted file mode 100644 index 183b6f494913..000000000000 --- a/crates/swc_ecma_lexer/examples/lexer.rs +++ /dev/null @@ -1,65 +0,0 @@ -use std::{ffi::OsStr, path::Path}; - -use swc_common::{ - errors::{ColorConfig, Handler}, - input::StringInput, - sync::Lrc, - SourceMap, -}; -use swc_ecma_lexer::{lexer, lexer::Lexer, EsSyntax, Syntax, TsSyntax}; - -fn main() { - let cm: Lrc = Default::default(); - let handler = Handler::with_tty_emitter(ColorConfig::Auto, true, false, Some(cm.clone())); - - // read file path from command line argument or use a default - let file_path = std::env::args() - .nth(1) - .unwrap_or_else(|| "test.js".to_string()); - - let file_path = Path::new(&file_path); - - let ext = file_path.extension().map(OsStr::to_ascii_lowercase); - let is_ts = ext.as_ref().is_some_and(|e| { - e == "ts" || e == "tsx" || e == "mts" || e == "cts" || e == "mtsx" || e == "ctsx" - }); - let is_d_ts = is_ts - && file_path - .file_name() - .and_then(OsStr::to_str) - .is_some_and(|s| { - let mut iter = s.rsplit('.'); - - if iter.next() != Some("ts") { - return false; - } - - iter.next() == Some("d") || iter.next() == Some("d") - }); - - let is_jsx = ext - .as_ref() - .is_some_and(|e| e == "jsx" || e == "tsx" || e == "mjsx" || e == "cjsx"); - - let fm = cm.load_file(file_path).expect("failed to load test.ts"); - - let syntax = if is_ts { - Syntax::Typescript(TsSyntax { - tsx: is_jsx, - dts: is_d_ts, - ..Default::default() - }) - } else { - Syntax::Es(EsSyntax { - jsx: is_jsx, - ..Default::default() - }) - }; - - let l = Lexer::new(syntax, Default::default(), StringInput::from(&*fm), None); - - let tokens = lexer(l) - .map_err(|e| e.into_diagnostic(&handler).emit()) - .expect("Failed to lex."); - println!("Tokens: {tokens:#?}",); -} diff --git a/crates/swc_ecma_lexer/src/common/context.rs b/crates/swc_ecma_lexer/src/common/context.rs deleted file mode 100644 index fff4240b327e..000000000000 --- a/crates/swc_ecma_lexer/src/common/context.rs +++ /dev/null @@ -1,71 +0,0 @@ -bitflags::bitflags! { - #[derive(Debug, Clone, Copy, Default)] - pub struct Context: u32 { - - /// `true` while backtracking - const IgnoreError = 1 << 0; - - /// Is in module code? - const Module = 1 << 1; - const CanBeModule = 1 << 2; - const Strict = 1 << 3; - - const ForLoopInit = 1 << 4; - const ForAwaitLoopInit = 1 << 5; - - const IncludeInExpr = 1 << 6; - /// If true, await expression is parsed, and "await" is treated as a - /// keyword. - const InAsync = 1 << 7; - /// If true, yield expression is parsed, and "yield" is treated as a - /// keyword. - const InGenerator = 1 << 8; - - /// If true, await is treated as a keyword. - const InStaticBlock = 1 << 9; - - const IsContinueAllowed = 1 << 10; - const IsBreakAllowed = 1 << 11; - - const InType = 1 << 12; - /// Typescript extension. - const ShouldNotLexLtOrGtAsType = 1 << 13; - /// Typescript extension. - const InDeclare = 1 << 14; - - /// If true, `:` should not be treated as a type annotation. - const InCondExpr = 1 << 15; - const WillExpectColonForCond = 1 << 16; - - const InClass = 1 << 17; - - const InClassField = 1 << 18; - - const InFunction = 1 << 19; - - /// This indicates current scope or the scope out of arrow function is - /// function declaration or function expression or not. - const InsideNonArrowFunctionScope = 1 << 20; - - const InParameters = 1 << 21; - - const HasSuperClass = 1 << 22; - - const InPropertyName = 1 << 23; - - const InForcedJsxContext = 1 << 24; - - // If true, allow super.x and super[x] - const AllowDirectSuper = 1 << 25; - - const IgnoreElseClause = 1 << 26; - - const DisallowConditionalTypes = 1 << 27; - - const AllowUsingDecl = 1 << 28; - - const TopLevel = 1 << 29; - - const TsModuleBlock = 1 << 30; - } -} diff --git a/crates/swc_ecma_lexer/src/common/input.rs b/crates/swc_ecma_lexer/src/common/input.rs deleted file mode 100644 index 74cb730edd50..000000000000 --- a/crates/swc_ecma_lexer/src/common/input.rs +++ /dev/null @@ -1,56 +0,0 @@ -use swc_common::BytePos; -use swc_ecma_ast::EsVersion; - -use super::context::Context; -use crate::{common::syntax::SyntaxFlags, error::Error, lexer}; - -/// Clone should be cheap if you are parsing typescript because typescript -/// syntax requires backtracking. -pub trait Tokens: Clone + Iterator { - type Checkpoint; - - fn set_ctx(&mut self, ctx: Context); - fn ctx(&self) -> Context; - fn ctx_mut(&mut self) -> &mut Context; - fn syntax(&self) -> SyntaxFlags; - fn target(&self) -> EsVersion; - - fn checkpoint_save(&self) -> Self::Checkpoint; - fn checkpoint_load(&mut self, checkpoint: Self::Checkpoint); - - fn start_pos(&self) -> BytePos { - BytePos(0) - } - - fn set_expr_allowed(&mut self, allow: bool); - fn set_next_regexp(&mut self, start: Option); - - fn token_context(&self) -> &lexer::TokenContexts; - fn token_context_mut(&mut self) -> &mut lexer::TokenContexts; - fn set_token_context(&mut self, c: lexer::TokenContexts); - - /// Implementors should use Rc>>. - /// - /// It is required because parser should backtrack while parsing typescript - /// code. - fn add_error(&mut self, error: Error); - - /// Add an error which is valid syntax in script mode. - /// - /// This errors should be dropped if it's not a module. - /// - /// Implementor should check for if [Context].module, and buffer errors if - /// module is false. Also, implementors should move errors to the error - /// buffer on set_ctx if the parser mode become module mode. - fn add_module_mode_error(&mut self, error: Error); - - fn end_pos(&self) -> BytePos; - - fn take_errors(&mut self) -> Vec; - - /// If the program was parsed as a script, this contains the module - /// errors should the program be identified as a module in the future. - fn take_script_module_errors(&mut self) -> Vec; - fn update_token_flags(&mut self, f: impl FnOnce(&mut lexer::TokenFlags)); - fn token_flags(&self) -> lexer::TokenFlags; -} diff --git a/crates/swc_ecma_lexer/src/common/lexer/char.rs b/crates/swc_ecma_lexer/src/common/lexer/char.rs deleted file mode 100644 index 62f4e4d08a49..000000000000 --- a/crates/swc_ecma_lexer/src/common/lexer/char.rs +++ /dev/null @@ -1,87 +0,0 @@ -/// Implemented for `u8` - operates on bytes for performance. -pub trait CharExt: Copy { - fn to_char(self) -> Option; - - /// Test whether a given byte/character starts an identifier. - /// - /// https://tc39.github.io/ecma262/#prod-IdentifierStart - #[inline] - fn is_ident_start(self) -> bool { - let c = match self.to_char() { - Some(c) => c, - None => return false, - }; - swc_ecma_ast::Ident::is_valid_start(c) - } - - /// Test whether a given byte/character is part of an identifier. - #[inline] - fn is_ident_part(self) -> bool { - let c = match self.to_char() { - Some(c) => c, - None => return false, - }; - swc_ecma_ast::Ident::is_valid_continue(c) - } - - /// See https://tc39.github.io/ecma262/#sec-line-terminators - #[inline] - fn is_line_terminator(self) -> bool { - let c = match self.to_char() { - Some(c) => c, - None => return false, - }; - matches!(c, '\r' | '\n' | '\u{2028}' | '\u{2029}') - } - - /// See https://tc39.github.io/ecma262/#sec-literals-string-literals - #[inline] - fn is_line_break(self) -> bool { - let c = match self.to_char() { - Some(c) => c, - None => return false, - }; - matches!(c, '\r' | '\n') - } - - /// See https://tc39.github.io/ecma262/#sec-white-space - #[inline] - fn is_ws(self) -> bool { - let c = match self.to_char() { - Some(c) => c, - None => return false, - }; - match c { - '\u{0009}' | '\u{000b}' | '\u{000c}' | '\u{0020}' | '\u{00a0}' | '\u{feff}' => true, - _ => { - if self.is_line_terminator() { - // NOTE: Line terminator is not whitespace. - false - } else { - c.is_whitespace() - } - } - } - } -} - -impl CharExt for u8 { - #[inline(always)] - fn to_char(self) -> Option { - // For ASCII bytes, this is a fast path - if self <= 0x7f { - Some(self as char) - } else { - // For non-ASCII bytes, we can't convert a single byte to a char - // The caller should use cur_as_char() on the Input trait instead - None - } - } -} - -impl CharExt for char { - #[inline(always)] - fn to_char(self) -> Option { - Some(self) - } -} diff --git a/crates/swc_ecma_lexer/src/common/lexer/comments_buffer.rs b/crates/swc_ecma_lexer/src/common/lexer/comments_buffer.rs deleted file mode 100644 index e13844e61149..000000000000 --- a/crates/swc_ecma_lexer/src/common/lexer/comments_buffer.rs +++ /dev/null @@ -1,21 +0,0 @@ -use swc_common::{comments::Comment, BytePos}; - -#[derive(Debug, Clone)] -pub struct BufferedComment { - pub kind: BufferedCommentKind, - pub pos: BytePos, - pub comment: Comment, -} - -#[derive(Debug, Clone, Copy)] -pub enum BufferedCommentKind { - Leading, - Trailing, -} - -pub trait CommentsBufferTrait { - fn push_comment(&mut self, comment: BufferedComment); - fn push_pending(&mut self, comment: Comment); - fn pending_to_comment(&mut self, kind: BufferedCommentKind, pos: BytePos); - fn take_comments(&mut self) -> impl Iterator + '_; -} diff --git a/crates/swc_ecma_lexer/src/common/lexer/jsx.rs b/crates/swc_ecma_lexer/src/common/lexer/jsx.rs deleted file mode 100644 index e43c83d35ccb..000000000000 --- a/crates/swc_ecma_lexer/src/common/lexer/jsx.rs +++ /dev/null @@ -1,270 +0,0 @@ -macro_rules! xhtml { - ( - $( - $i:ident : $s:expr, - )* - ) => { - pub(super) fn xhtml(s: &str) -> Option { - match s{ - $(stringify!($i) => Some($s),)* - _ => None, - } - } - }; -} - -xhtml!( - quot: '\u{0022}', - amp: '&', - apos: '\u{0027}', - lt: '<', - gt: '>', - nbsp: '\u{00A0}', - iexcl: '\u{00A1}', - cent: '\u{00A2}', - pound: '\u{00A3}', - curren: '\u{00A4}', - yen: '\u{00A5}', - brvbar: '\u{00A6}', - sect: '\u{00A7}', - uml: '\u{00A8}', - copy: '\u{00A9}', - ordf: '\u{00AA}', - laquo: '\u{00AB}', - not: '\u{00AC}', - shy: '\u{00AD}', - reg: '\u{00AE}', - macr: '\u{00AF}', - deg: '\u{00B0}', - plusmn: '\u{00B1}', - sup2: '\u{00B2}', - sup3: '\u{00B3}', - acute: '\u{00B4}', - micro: '\u{00B5}', - para: '\u{00B6}', - middot: '\u{00B7}', - cedil: '\u{00B8}', - sup1: '\u{00B9}', - ordm: '\u{00BA}', - raquo: '\u{00BB}', - frac14: '\u{00BC}', - frac12: '\u{00BD}', - frac34: '\u{00BE}', - iquest: '\u{00BF}', - Agrave: '\u{00C0}', - Aacute: '\u{00C1}', - Acirc: '\u{00C2}', - Atilde: '\u{00C3}', - Auml: '\u{00C4}', - Aring: '\u{00C5}', - AElig: '\u{00C6}', - Ccedil: '\u{00C7}', - Egrave: '\u{00C8}', - Eacute: '\u{00C9}', - Ecirc: '\u{00CA}', - Euml: '\u{00CB}', - Igrave: '\u{00CC}', - Iacute: '\u{00CD}', - Icirc: '\u{00CE}', - Iuml: '\u{00CF}', - ETH: '\u{00D0}', - Ntilde: '\u{00D1}', - Ograve: '\u{00D2}', - Oacute: '\u{00D3}', - Ocirc: '\u{00D4}', - Otilde: '\u{00D5}', - Ouml: '\u{00D6}', - times: '\u{00D7}', - Oslash: '\u{00D8}', - Ugrave: '\u{00D9}', - Uacute: '\u{00DA}', - Ucirc: '\u{00DB}', - Uuml: '\u{00DC}', - Yacute: '\u{00DD}', - THORN: '\u{00DE}', - szlig: '\u{00DF}', - agrave: '\u{00E0}', - aacute: '\u{00E1}', - acirc: '\u{00E2}', - atilde: '\u{00E3}', - auml: '\u{00E4}', - aring: '\u{00E5}', - aelig: '\u{00E6}', - ccedil: '\u{00E7}', - egrave: '\u{00E8}', - eacute: '\u{00E9}', - ecirc: '\u{00EA}', - euml: '\u{00EB}', - igrave: '\u{00EC}', - iacute: '\u{00ED}', - icirc: '\u{00EE}', - iuml: '\u{00EF}', - eth: '\u{00F0}', - ntilde: '\u{00F1}', - ograve: '\u{00F2}', - oacute: '\u{00F3}', - ocirc: '\u{00F4}', - otilde: '\u{00F5}', - ouml: '\u{00F6}', - divide: '\u{00F7}', - oslash: '\u{00F8}', - ugrave: '\u{00F9}', - uacute: '\u{00FA}', - ucirc: '\u{00FB}', - uuml: '\u{00FC}', - yacute: '\u{00FD}', - thorn: '\u{00FE}', - yuml: '\u{00FF}', - OElig: '\u{0152}', - oelig: '\u{0153}', - Scaron: '\u{0160}', - scaron: '\u{0161}', - Yuml: '\u{0178}', - fnof: '\u{0192}', - circ: '\u{02C6}', - tilde: '\u{02DC}', - Alpha: '\u{0391}', - Beta: '\u{0392}', - Gamma: '\u{0393}', - Delta: '\u{0394}', - Epsilon: '\u{0395}', - Zeta: '\u{0396}', - Eta: '\u{0397}', - Theta: '\u{0398}', - Iota: '\u{0399}', - Kappa: '\u{039A}', - Lambda: '\u{039B}', - Mu: '\u{039C}', - Nu: '\u{039D}', - Xi: '\u{039E}', - Omicron: '\u{039F}', - Pi: '\u{03A0}', - Rho: '\u{03A1}', - Sigma: '\u{03A3}', - Tau: '\u{03A4}', - Upsilon: '\u{03A5}', - Phi: '\u{03A6}', - Chi: '\u{03A7}', - Psi: '\u{03A8}', - Omega: '\u{03A9}', - alpha: '\u{03B1}', - beta: '\u{03B2}', - gamma: '\u{03B3}', - delta: '\u{03B4}', - epsilon: '\u{03B5}', - zeta: '\u{03B6}', - eta: '\u{03B7}', - theta: '\u{03B8}', - iota: '\u{03B9}', - kappa: '\u{03BA}', - lambda: '\u{03BB}', - mu: '\u{03BC}', - nu: '\u{03BD}', - xi: '\u{03BE}', - omicron: '\u{03BF}', - pi: '\u{03C0}', - rho: '\u{03C1}', - sigmaf: '\u{03C2}', - sigma: '\u{03C3}', - tau: '\u{03C4}', - upsilon: '\u{03C5}', - phi: '\u{03C6}', - chi: '\u{03C7}', - psi: '\u{03C8}', - omega: '\u{03C9}', - thetasym: '\u{03D1}', - upsih: '\u{03D2}', - piv: '\u{03D6}', - ensp: '\u{2002}', - emsp: '\u{2003}', - thinsp: '\u{2009}', - zwnj: '\u{200C}', - zwj: '\u{200D}', - lrm: '\u{200E}', - rlm: '\u{200F}', - ndash: '\u{2013}', - mdash: '\u{2014}', - lsquo: '\u{2018}', - rsquo: '\u{2019}', - sbquo: '\u{201A}', - ldquo: '\u{201C}', - rdquo: '\u{201D}', - bdquo: '\u{201E}', - dagger: '\u{2020}', - Dagger: '\u{2021}', - bull: '\u{2022}', - hellip: '\u{2026}', - permil: '\u{2030}', - prime: '\u{2032}', - Prime: '\u{2033}', - lsaquo: '\u{2039}', - rsaquo: '\u{203A}', - oline: '\u{203E}', - frasl: '\u{2044}', - euro: '\u{20AC}', - image: '\u{2111}', - weierp: '\u{2118}', - real: '\u{211C}', - trade: '\u{2122}', - alefsym: '\u{2135}', - larr: '\u{2190}', - uarr: '\u{2191}', - rarr: '\u{2192}', - darr: '\u{2193}', - harr: '\u{2194}', - crarr: '\u{21B5}', - lArr: '\u{21D0}', - uArr: '\u{21D1}', - rArr: '\u{21D2}', - dArr: '\u{21D3}', - hArr: '\u{21D4}', - forall: '\u{2200}', - part: '\u{2202}', - exist: '\u{2203}', - empty: '\u{2205}', - nabla: '\u{2207}', - isin: '\u{2208}', - notin: '\u{2209}', - ni: '\u{220B}', - prod: '\u{220F}', - sum: '\u{2211}', - minus: '\u{2212}', - lowast: '\u{2217}', - radic: '\u{221A}', - prop: '\u{221D}', - infin: '\u{221E}', - ang: '\u{2220}', - and: '\u{2227}', - or: '\u{2228}', - cap: '\u{2229}', - cup: '\u{222A}', - int: '\u{222B}', - there4: '\u{2234}', - sim: '\u{223C}', - cong: '\u{2245}', - asymp: '\u{2248}', - ne: '\u{2260}', - equiv: '\u{2261}', - le: '\u{2264}', - ge: '\u{2265}', - sub: '\u{2282}', - sup: '\u{2283}', - nsub: '\u{2284}', - sube: '\u{2286}', - supe: '\u{2287}', - oplus: '\u{2295}', - otimes: '\u{2297}', - perp: '\u{22A5}', - sdot: '\u{22C5}', - lceil: '\u{2308}', - rceil: '\u{2309}', - lfloor: '\u{230A}', - rfloor: '\u{230B}', - lang: '\u{2329}', - rang: '\u{232A}', - loz: '\u{25CA}', - spades: '\u{2660}', - clubs: '\u{2663}', - hearts: '\u{2665}', - diams: '\u{2666}', -); diff --git a/crates/swc_ecma_lexer/src/common/lexer/mod.rs b/crates/swc_ecma_lexer/src/common/lexer/mod.rs deleted file mode 100644 index 1a2670ecda98..000000000000 --- a/crates/swc_ecma_lexer/src/common/lexer/mod.rs +++ /dev/null @@ -1,2256 +0,0 @@ -use std::borrow::Cow; - -use char::CharExt; -use compact_str::CompactString; -use either::Either::{self, Left, Right}; -use num_bigint::BigInt as BigIntValue; -use state::State; -use swc_atoms::{ - wtf8::{CodePoint, Wtf8, Wtf8Buf}, - Atom, -}; -use swc_common::{ - comments::{Comment, CommentKind}, - input::{Input, StringInput}, - BytePos, Span, -}; -use swc_ecma_ast::{EsVersion, Ident}; - -use self::jsx::xhtml; -use super::{context::Context, input::Tokens}; -use crate::{ - common::lexer::{ - comments_buffer::{BufferedComment, BufferedCommentKind, CommentsBufferTrait}, - number::{parse_integer, LazyInteger}, - }, - error::SyntaxError, - lexer::TokenFlags, -}; - -pub mod char; -pub mod comments_buffer; -mod jsx; -pub mod number; -mod search; -pub mod state; -pub mod token; -pub mod whitespace; - -use token::TokenFactory; - -// Byte-search utilities -use self::search::SafeByteMatchTable; -use crate::{byte_search, safe_byte_match_table}; - -// ===== Byte match tables for comment scanning ===== -// Irregular line breaks - '\u{2028}' (LS) and '\u{2029}' (PS) -const LS_OR_PS_FIRST: u8 = 0xe2; -const LS_BYTES_2_AND_3: [u8; 2] = [0x80, 0xa8]; -const PS_BYTES_2_AND_3: [u8; 2] = [0x80, 0xa9]; - -static LINE_BREAK_TABLE: SafeByteMatchTable = - safe_byte_match_table!(|b| matches!(b, b'\n' | b'\r' | LS_OR_PS_FIRST)); - -static BLOCK_COMMENT_SCAN_TABLE: SafeByteMatchTable = - safe_byte_match_table!(|b| { matches!(b, b'*' | b'\n' | b'\r' | LS_OR_PS_FIRST) }); - -static DOUBLE_QUOTE_STRING_END_TABLE: SafeByteMatchTable = - safe_byte_match_table!(|b| matches!(b, b'"' | b'\n' | b'\\' | b'\r')); -static SINGLE_QUOTE_STRING_END_TABLE: SafeByteMatchTable = - safe_byte_match_table!(|b| matches!(b, b'\'' | b'\n' | b'\\' | b'\r')); - -static NOT_ASCII_ID_CONTINUE_TABLE: SafeByteMatchTable = - safe_byte_match_table!(|b| !(b.is_ascii_alphanumeric() || b == b'_' || b == b'$')); - -static TEMPLATE_LITERAL_TABLE: SafeByteMatchTable = - safe_byte_match_table!(|b| matches!(b, b'$' | b'`' | b'\\' | b'\r')); - -/// Converts UTF-16 surrogate pair to Unicode code point. -/// `https://tc39.es/ecma262/#sec-utf16decodesurrogatepair` -#[inline] -const fn pair_to_code_point(high: u32, low: u32) -> u32 { - (high - 0xd800) * 0x400 + low - 0xdc00 + 0x10000 -} - -/// A Unicode escape sequence. -/// -/// `\u Hex4Digits`, `\u Hex4Digits \u Hex4Digits`, or `\u{ HexDigits }`. -#[derive(Debug)] -pub enum UnicodeEscape { - // `\u Hex4Digits` or `\u{ HexDigits }`, which forms a valid Unicode code point. - // Char cannot be in range 0xD800..=0xDFFF. - CodePoint(char), - // `\u Hex4Digits \u Hex4Digits`, which forms a valid Unicode astral code point. - // Char is in the range 0x10000..=0x10FFFF. - SurrogatePair(char), - // `\u Hex4Digits` or `\u{ HexDigits }`, which forms an invalid Unicode code point. - // Code unit is in the range 0xD800..=0xDFFF. - LoneSurrogate(u32), -} - -impl From for CodePoint { - fn from(value: UnicodeEscape) -> Self { - match value { - UnicodeEscape::CodePoint(c) | UnicodeEscape::SurrogatePair(c) => { - CodePoint::from_char(c) - } - UnicodeEscape::LoneSurrogate(u) => unsafe { CodePoint::from_u32_unchecked(u) }, - } - } -} - -pub type LexResult = swc_ecma_parser::lexer::LexResult; - -fn remove_underscore(s: &str, has_underscore: bool) -> Cow<'_, str> { - if has_underscore { - debug_assert!(s.contains('_')); - s.chars().filter(|&c| c != '_').collect::().into() - } else { - debug_assert!(!s.contains('_')); - Cow::Borrowed(s) - } -} - -pub trait Lexer<'a, TokenAndSpan>: Tokens + Sized { - type State: self::state::State; - type Token: token::TokenFactory<'a, TokenAndSpan, Self, Lexer = Self>; - type CommentsBuffer: CommentsBufferTrait; - - fn input(&self) -> &StringInput<'a>; - fn input_mut(&mut self) -> &mut StringInput<'a>; - fn state(&self) -> &Self::State; - fn state_mut(&mut self) -> &mut Self::State; - fn comments(&self) -> Option<&'a dyn swc_common::comments::Comments>; - fn comments_buffer(&self) -> Option<&Self::CommentsBuffer>; - fn comments_buffer_mut(&mut self) -> Option<&mut Self::CommentsBuffer>; - /// # Safety - /// - /// We know that the start and the end are valid - unsafe fn input_slice(&mut self, start: BytePos, end: BytePos) -> &'a str; - fn input_uncons_while(&mut self, f: impl FnMut(char) -> bool) -> &'a str; - fn atom<'b>(&self, s: impl Into>) -> swc_atoms::Atom; - fn wtf8_atom<'b>(&self, s: impl Into>) -> swc_atoms::Wtf8Atom; - fn push_error(&mut self, error: crate::error::Error); - - #[inline(always)] - #[allow(clippy::misnamed_getters)] - fn had_line_break_before_last(&self) -> bool { - self.state().had_line_break() - } - - #[inline(always)] - fn span(&self, start: BytePos) -> Span { - let end = self.last_pos(); - if cfg!(debug_assertions) && start > end { - unreachable!( - "assertion failed: (span.start <= span.end). - start = {}, end = {}", - start.0, end.0 - ) - } - Span { lo: start, hi: end } - } - - #[inline(always)] - fn is(&self, c: u8) -> bool { - self.input().is_byte(c) - } - - #[inline(always)] - fn is_str(&self, s: &str) -> bool { - self.input().is_str(s) - } - - #[inline(always)] - fn eat(&mut self, c: u8) -> bool { - // Safety: All callers pass ASCII bytes. - unsafe { self.input_mut().eat_byte(c) } - } - - #[inline(always)] - fn cur(&self) -> Option { - self.input().cur() - } - - #[inline(always)] - fn peek(&self) -> Option { - self.input().peek() - } - - #[inline(always)] - fn peek_ahead(&self) -> Option { - self.input().peek_ahead() - } - - #[inline(always)] - fn cur_as_char(&self) -> Option { - self.input().cur_as_char() - } - - #[inline(always)] - fn bump(&mut self) { - let c = self.cur_as_char().unwrap(); - unsafe { - self.input_mut().bump_bytes(c.len_utf8()); - } - } - - #[inline(always)] - fn cur_pos(&self) -> BytePos { - self.input().cur_pos() - } - - #[inline(always)] - fn last_pos(&self) -> BytePos { - self.input().last_pos() - } - - /// Shorthand for `let span = self.span(start); self.error_span(span)` - #[cold] - #[inline(never)] - fn error(&self, start: BytePos, kind: SyntaxError) -> LexResult { - let span = self.span(start); - self.error_span(span, kind) - } - - #[cold] - #[inline(never)] - fn error_span(&self, span: Span, kind: SyntaxError) -> LexResult { - Err(crate::error::Error::new(span, kind)) - } - - #[cold] - #[inline(never)] - fn emit_error(&mut self, start: BytePos, kind: SyntaxError) { - let span = self.span(start); - self.emit_error_span(span, kind) - } - - #[cold] - #[inline(never)] - fn emit_error_span(&mut self, span: Span, kind: SyntaxError) { - if self.ctx().contains(Context::IgnoreError) { - return; - } - #[cfg(debug_assertions)] - tracing::warn!("Lexer error at {:?}", span); - let err = crate::error::Error::new(span, kind); - self.push_error(err); - } - - #[cold] - #[inline(never)] - fn emit_strict_mode_error(&mut self, start: BytePos, kind: SyntaxError) { - let span = self.span(start); - if self.ctx().contains(Context::Strict) { - self.emit_error_span(span, kind); - } else { - let err = crate::error::Error::new(span, kind); - self.add_module_mode_error(err); - } - } - - #[cold] - #[inline(never)] - fn emit_module_mode_error(&mut self, start: BytePos, kind: SyntaxError) { - let span = self.span(start); - let err = crate::error::Error::new(span, kind); - self.add_module_mode_error(err); - } - - #[inline(never)] - fn skip_line_comment(&mut self, start_skip: usize) { - // Position after the initial `//` (or similar) - let start = self.cur_pos(); - unsafe { - self.input_mut().bump_bytes(start_skip); - } - let slice_start = self.cur_pos(); - - // foo // comment for foo - // bar - // - // foo - // // comment for bar - // bar - // - let is_for_next = - self.state().had_line_break() || !self.state().can_have_trailing_line_comment(); - - // Fast search for line-terminator - byte_search! { - lexer: self, - table: LINE_BREAK_TABLE, - continue_if: (matched_byte, pos_offset) { - if matched_byte != LS_OR_PS_FIRST { - // '\r' or '\n' - definitely a line terminator - false - } else { - // 0xE2 - could be LS/PS or some other Unicode character - // Check the next 2 bytes to see if it's really LS/PS - let current_slice = self.input().as_str(); - let byte_pos = pos_offset; - if byte_pos + 2 < current_slice.len() { - let bytes = current_slice.as_bytes(); - let next2 = [bytes[byte_pos + 1], bytes[byte_pos + 2]]; - if next2 == LS_BYTES_2_AND_3 || next2 == PS_BYTES_2_AND_3 { - // It's a real line terminator - false - } else { - // Some other Unicode character starting with 0xE2 - true - } - } else { - // Not enough bytes for full LS/PS sequence - true - } - } - }, - handle_eof: { - // Reached EOF – entire remainder is comment - let end = self.input().end_pos(); - - if self.comments_buffer().is_some() { - let s = unsafe { self.input_slice(slice_start, end) }; - let cmt = swc_common::comments::Comment { - kind: swc_common::comments::CommentKind::Line, - span: Span::new_with_checked(start, end), - text: self.atom(s), - }; - - if is_for_next { - self.comments_buffer_mut().unwrap().push_pending(cmt); - } else { - let pos = self.state().prev_hi(); - self.comments_buffer_mut().unwrap().push_comment(BufferedComment { - kind: BufferedCommentKind::Trailing, - pos, - comment: cmt, - }); - } - } - - return; - } - }; - - // Current position is at the line terminator - let end = self.cur_pos(); - - // Create and process slice only if comments need to be stored - if self.comments_buffer().is_some() { - let s = unsafe { - // Safety: We know that the start and the end are valid - self.input_slice(slice_start, end) - }; - let cmt = swc_common::comments::Comment { - kind: swc_common::comments::CommentKind::Line, - span: Span::new_with_checked(start, end), - text: self.atom(s), - }; - - if is_for_next { - self.comments_buffer_mut().unwrap().push_pending(cmt); - } else { - let pos = self.state().prev_hi(); - self.comments_buffer_mut() - .unwrap() - .push_comment(BufferedComment { - kind: BufferedCommentKind::Trailing, - pos, - comment: cmt, - }); - } - } - - unsafe { - // Safety: We got end from self.input - self.input_mut().reset_to(end); - } - } - - /// Expects current char to be '/' and next char to be '*'. - fn skip_block_comment(&mut self) { - let start = self.cur_pos(); - - debug_assert_eq!(self.cur(), Some(b'/')); - debug_assert_eq!(self.peek(), Some(b'*')); - - // Consume initial "/*" - unsafe { - self.input_mut().bump_bytes(2); - } - - // jsdoc - let slice_start = self.cur_pos(); - - let had_line_break_before_last = self.had_line_break_before_last(); - let mut should_mark_had_line_break = false; - - loop { - let matched_byte = byte_search! { - lexer: self, - table: BLOCK_COMMENT_SCAN_TABLE, - continue_if: (matched_byte, pos_offset) { - if matched_byte == LS_OR_PS_FIRST { - // 0xE2 - could be LS/PS or some other Unicode character - let current_slice = self.input().as_str(); - let byte_pos = pos_offset; - if byte_pos + 2 < current_slice.len() { - let bytes = current_slice.as_bytes(); - let next2 = [bytes[byte_pos + 1], bytes[byte_pos + 2]]; - if next2 == LS_BYTES_2_AND_3 || next2 == PS_BYTES_2_AND_3 { - // It's a real line terminator - don't continue - false - } else { - // Some other Unicode character starting with 0xE2 - true - } - } else { - // Not enough bytes for full LS/PS sequence - true - } - } else { - // '*', '\r', or '\n' - don't continue - false - } - }, - handle_eof: { - if should_mark_had_line_break { - self.state_mut().mark_had_line_break(); - } - let end_pos = self.input().end_pos(); - let span = Span::new_with_checked(end_pos, end_pos); - self.emit_error_span(span, SyntaxError::UnterminatedBlockComment); - return; - } - }; - - match matched_byte { - b'*' => { - if self.peek() == Some(b'/') { - // Consume "*/" - unsafe { - self.input_mut().bump_bytes(2); - } - - if should_mark_had_line_break { - self.state_mut().mark_had_line_break(); - } - - let end = self.cur_pos(); - - // Decide trailing / leading - let mut is_for_next = - had_line_break_before_last || !self.state().can_have_trailing_comment(); - - // If next char is ';' without newline, treat as trailing - if !had_line_break_before_last && self.input().is_byte(b';') { - is_for_next = false; - } - - if self.comments_buffer().is_some() { - let src = unsafe { - // Safety: We got slice_start and end from self.input so those are - // valid. - self.input_mut().slice(slice_start, end) - }; - let s = &src[..src.len() - 2]; - let cmt = Comment { - kind: CommentKind::Block, - span: Span::new_with_checked(start, end), - text: self.atom(s), - }; - - if is_for_next { - self.comments_buffer_mut().unwrap().push_pending(cmt); - } else { - let pos = self.state().prev_hi(); - self.comments_buffer_mut() - .unwrap() - .push_comment(BufferedComment { - kind: BufferedCommentKind::Trailing, - pos, - comment: cmt, - }); - } - } - - return; - } else { - // Just a lone '*', consume it and continue. - self.bump(); - } - } - b'\n' => { - should_mark_had_line_break = true; - self.bump(); - } - b'\r' => { - should_mark_had_line_break = true; - self.bump(); - if self.peek() == Some(b'\n') { - self.bump(); - } - } - _ => { - // Unicode line terminator (LS/PS) or other character - if let Some('\u{2028}' | '\u{2029}') = self.cur_as_char() { - should_mark_had_line_break = true; - } - self.bump(); - } - } - } - } - - /// Skip comments or whitespaces. - /// - /// See https://tc39.github.io/ecma262/#sec-white-space - #[inline(never)] - fn skip_space(&mut self) { - loop { - let (offset, newline) = { - let mut skip = self::whitespace::SkipWhitespace { - input: self.input().as_str(), - newline: false, - offset: 0, - }; - - skip.scan(); - - (skip.offset, skip.newline) - }; - - unsafe { - self.input_mut().bump_bytes(offset as usize); - } - if newline { - self.state_mut().mark_had_line_break(); - } - - if LEX_COMMENTS && self.input().is_byte(b'/') { - if let Some(c) = self.peek() { - if c == b'/' { - self.skip_line_comment(2); - continue; - } else if c == b'*' { - self.skip_block_comment(); - continue; - } - } - } - - break; - } - } - - /// Ensure that ident cannot directly follow numbers. - fn ensure_not_ident(&mut self) -> LexResult<()> { - match self.cur() { - Some(c) if c.is_ident_start() => { - let span = pos_span(self.cur_pos()); - self.error_span(span, SyntaxError::IdentAfterNum)? - } - _ => Ok(()), - } - } - - fn make_legacy_octal(&mut self, start: BytePos, val: f64) -> LexResult { - self.ensure_not_ident()?; - if self.syntax().typescript() && self.target() >= EsVersion::Es5 { - self.emit_error(start, SyntaxError::TS1085); - } - self.emit_strict_mode_error(start, SyntaxError::LegacyOctal); - Ok(val) - } - - /// `op`- |total, radix, value| -> (total * radix + value, continue) - fn read_digits( - &mut self, - mut op: F, - allow_num_separator: bool, - has_underscore: &mut bool, - ) -> LexResult - where - F: FnMut(Ret, u8, u32) -> LexResult<(Ret, bool)>, - Ret: Copy + Default, - { - debug_assert!( - RADIX == 2 || RADIX == 8 || RADIX == 10 || RADIX == 16, - "radix for read_int should be one of 2, 8, 10, 16, but got {RADIX}" - ); - - if cfg!(feature = "debug") { - #[cfg(debug_assertions)] - tracing::trace!("read_digits(radix = {}), cur = {:?}", RADIX, self.cur()); - } - - let start = self.cur_pos(); - let mut total: Ret = Default::default(); - let mut prev = None; - - while let Some(c) = self.cur() { - if c == b'_' { - *has_underscore = true; - if allow_num_separator { - let is_allowed = |c: Option| { - let Some(c) = c else { - return false; - }; - (c as char).is_digit(RADIX as _) - }; - let is_forbidden = |c: Option| { - let Some(c) = c else { - return false; - }; - - if RADIX == 16 { - matches!(c, b'.' | b'X' | b'_' | b'x') - } else { - matches!(c, b'.' | b'B' | b'E' | b'O' | b'_' | b'b' | b'e' | b'o') - } - }; - - let next = self.input().peek(); - - if !is_allowed(next) || is_forbidden(prev) || is_forbidden(next) { - self.emit_error( - start, - SyntaxError::NumericSeparatorIsAllowedOnlyBetweenTwoDigits, - ); - } - - // Ignore this _ character - // Safety: cur() returns Some(c) where c is a valid char - self.bump(); - - continue; - } - } - - // e.g. (val for a) = 10 where radix = 16 - let val = if let Some(val) = (c as char).to_digit(RADIX as _) { - val - } else { - return Ok(total); - }; - - self.bump(); - - let (t, cont) = op(total, RADIX, val)?; - - total = t; - - if !cont { - return Ok(total); - } - - prev = Some(c); - } - - Ok(total) - } - - /// This can read long integers like - /// "13612536612375123612312312312312312312312". - /// - /// - Returned `bool` is `true` is there was `8` or `9`. - fn read_number_no_dot_as_str(&mut self) -> LexResult { - debug_assert!( - RADIX == 2 || RADIX == 8 || RADIX == 10 || RADIX == 16, - "radix for read_number_no_dot should be one of 2, 8, 10, 16, but got {RADIX}" - ); - let start = self.cur_pos(); - - let mut not_octal = false; - let mut read_any = false; - let mut has_underscore = false; - - self.read_digits::<_, (), RADIX>( - |_, _, v| { - read_any = true; - - if v == 8 || v == 9 { - not_octal = true; - } - - Ok(((), true)) - }, - true, - &mut has_underscore, - )?; - - if !read_any { - self.error(start, SyntaxError::ExpectedDigit { radix: RADIX })?; - } - - Ok(LazyInteger { - start, - end: self.cur_pos(), - not_octal, - has_underscore, - }) - } - - /// Reads an integer, octal integer, or floating-point number - fn read_number( - &mut self, - ) -> LexResult, Atom)>> { - debug_assert!(!(START_WITH_DOT && START_WITH_ZERO)); - debug_assert!(self.cur().is_some()); - - let start = self.cur_pos(); - let mut has_underscore = false; - - let lazy_integer = if START_WITH_DOT { - // first char is '.' - debug_assert!( - self.cur().is_some_and(|c| c == b'.'), - "read_number expects current char to be '.'" - ); - LazyInteger { - start, - end: start, - not_octal: true, - has_underscore: false, - } - } else { - debug_assert!(!START_WITH_DOT); - debug_assert!(!START_WITH_ZERO || self.cur().unwrap() == b'0'); - - // Use read_number_no_dot to support long numbers. - let lazy_integer = self.read_number_no_dot_as_str::<10>()?; - let s = unsafe { - // Safety: We got both start and end position from `self.input` - self.input_slice(lazy_integer.start, lazy_integer.end) - }; - - // legacy octal number is not allowed in bigint. - if (!START_WITH_ZERO || lazy_integer.end - lazy_integer.start == BytePos(1)) - && self.eat(b'n') - { - let end = self.cur_pos(); - let raw = unsafe { - // Safety: We got both start and end position from `self.input` - self.input_slice(start, end) - }; - let bigint_value = num_bigint::BigInt::parse_bytes(s.as_bytes(), 10).unwrap(); - return Ok(Either::Right((Box::new(bigint_value), self.atom(raw)))); - } - - if START_WITH_ZERO { - // TODO: I guess it would be okay if I don't use -ffast-math - // (or something like that), but needs review. - if s.as_bytes().iter().all(|&c| c == b'0') { - // If only one zero is used, it's decimal. - // And if multiple zero is used, it's octal. - // - // e.g. `0` is decimal (so it can be part of float) - // - // e.g. `000` is octal - if start.0 != self.last_pos().0 - 1 { - let end = self.cur_pos(); - let raw = unsafe { - // Safety: We got both start and end position from `self.input` - self.input_slice(start, end) - }; - let raw = self.atom(raw); - return self - .make_legacy_octal(start, 0f64) - .map(|value| Either::Left((value, raw))); - } - } else if lazy_integer.not_octal { - // if it contains '8' or '9', it's decimal. - self.emit_strict_mode_error(start, SyntaxError::LegacyDecimal); - } else { - // It's Legacy octal, and we should reinterpret value. - let s = remove_underscore(s, lazy_integer.has_underscore); - let val = parse_integer::<8>(&s); - let end = self.cur_pos(); - let raw = unsafe { - // Safety: We got both start and end position from `self.input` - self.input_slice(start, end) - }; - let raw = self.atom(raw); - return self - .make_legacy_octal(start, val) - .map(|value| Either::Left((value, raw))); - } - } - - lazy_integer - }; - - has_underscore |= lazy_integer.has_underscore; - // At this point, number cannot be an octal literal. - - let has_dot = self.cur() == Some(b'.'); - // `0.a`, `08.a`, `102.a` are invalid. - // - // `.1.a`, `.1e-4.a` are valid, - if has_dot { - self.bump(); - - // equal: if START_WITH_DOT { debug_assert!(xxxx) } - debug_assert!(!START_WITH_DOT || self.cur().is_some_and(|cur| cur.is_ascii_digit())); - - // Read numbers after dot - self.read_digits::<_, (), 10>(|_, _, _| Ok(((), true)), true, &mut has_underscore)?; - } - - let has_e = self.cur().is_some_and(|c| c == b'e' || c == b'E'); - // Handle 'e' and 'E' - // - // .5e1 = 5 - // 1e2 = 100 - // 1e+2 = 100 - // 1e-2 = 0.01 - if has_e { - self.bump(); // `e`/`E` - - let next = match self.cur() { - Some(next) => next, - None => { - let pos = self.cur_pos(); - self.error(pos, SyntaxError::NumLitTerminatedWithExp)? - } - }; - - if next == b'+' || next == b'-' { - self.bump(); // remove '+', '-' - } - - let lazy_integer = self.read_number_no_dot_as_str::<10>()?; - has_underscore |= lazy_integer.has_underscore; - } - - let val = if has_dot || has_e { - let end = self.cur_pos(); - let raw = unsafe { - // Safety: We got both start and end position from `self.input` - self.input_slice(start, end) - }; - - let raw = remove_underscore(raw, has_underscore); - raw.parse().expect("failed to parse float literal") - } else { - let s = unsafe { self.input_slice(lazy_integer.start, lazy_integer.end) }; - let s = remove_underscore(s, has_underscore); - parse_integer::<10>(&s) - }; - - self.ensure_not_ident()?; - - let end = self.cur_pos(); - let raw_str = unsafe { - // Safety: We got both start and end position from `self.input` - self.input_slice(start, end) - }; - Ok(Either::Left((val, raw_str.into()))) - } - - fn read_int_u32(&mut self, len: u8) -> LexResult> { - let start = self.state().start(); - - let mut count = 0; - let v = self.read_digits::<_, Option, RADIX>( - |opt: Option, radix, val| { - count += 1; - - let total = opt - .unwrap_or_default() - .checked_mul(radix as u32) - .and_then(|v| v.checked_add(val)) - .ok_or_else(|| { - let span = Span::new_with_checked(start, start); - crate::error::Error::new(span, SyntaxError::InvalidUnicodeEscape) - })?; - - Ok((Some(total), count != len)) - }, - true, - &mut false, - )?; - if len != 0 && count != len { - Ok(None) - } else { - Ok(v) - } - } - - /// Returns `Left(value)` or `Right(BigInt)` - fn read_radix_number( - &mut self, - ) -> LexResult, Atom)>> { - debug_assert!( - RADIX == 2 || RADIX == 8 || RADIX == 16, - "radix should be one of 2, 8, 16, but got {RADIX}" - ); - let start = self.cur_pos(); - - debug_assert_eq!(self.cur(), Some(b'0')); - self.bump(); - - debug_assert!(self - .cur() - .is_some_and(|c| matches!(c, b'b' | b'B' | b'o' | b'O' | b'x' | b'X'))); - self.bump(); - - let lazy_integer = self.read_number_no_dot_as_str::()?; - let has_underscore = lazy_integer.has_underscore; - - let s = unsafe { - // Safety: We got both start and end position from `self.input` - self.input_slice(lazy_integer.start, lazy_integer.end) - }; - if self.eat(b'n') { - let end = self.cur_pos(); - let raw = unsafe { - // Safety: We got both start and end position from `self.input` - self.input_slice(start, end) - }; - - let bigint_value = num_bigint::BigInt::parse_bytes(s.as_bytes(), RADIX as _).unwrap(); - return Ok(Either::Right((Box::new(bigint_value), self.atom(raw)))); - } - let s = remove_underscore(s, has_underscore); - let val = parse_integer::(&s); - - self.ensure_not_ident()?; - - let end = self.cur_pos(); - let raw = unsafe { - // Safety: We got both start and end position from `self.input` - self.input_slice(start, end) - }; - - Ok(Either::Left((val, self.atom(raw)))) - } - - /// Consume pending comments. - /// - /// This is called when the input is exhausted. - #[cold] - #[inline(never)] - fn consume_pending_comments(&mut self) { - if let Some(comments) = self.comments() { - let last = self.state().prev_hi(); - let start_pos = self.start_pos(); - let comments_buffer = self.comments_buffer_mut().unwrap(); - - // if the file had no tokens and no shebang, then treat any - // comments in the leading comments buffer as leading. - // Otherwise treat them as trailing. - let kind = if last == start_pos { - BufferedCommentKind::Leading - } else { - BufferedCommentKind::Trailing - }; - // move the pending to the leading or trailing - comments_buffer.pending_to_comment(kind, last); - - // now fill the user's passed in comments - for comment in comments_buffer.take_comments() { - match comment.kind { - BufferedCommentKind::Leading => { - comments.add_leading(comment.pos, comment.comment); - } - BufferedCommentKind::Trailing => { - comments.add_trailing(comment.pos, comment.comment); - } - } - } - } - } - - /// Read a JSX identifier (valid tag or attribute name). - /// - /// Optimized version since JSX identifiers can"t contain - /// escape characters and so can be read as single slice. - /// Also assumes that first character was already checked - /// by isIdentifierStart in readToken. - fn read_jsx_word(&mut self) -> LexResult { - debug_assert!(self.syntax().jsx()); - debug_assert!(self.input().cur().is_some_and(|c| c.is_ident_start())); - - let mut first = true; - let slice = self.input_uncons_while(|c| { - if first { - first = false; - c.is_ident_start() - } else { - c.is_ident_part() || c == '-' - } - }); - - Ok(Self::Token::jsx_name(slice, self)) - } - - fn read_jsx_entity(&mut self) -> LexResult<(char, String)> { - debug_assert!(self.syntax().jsx()); - - fn from_code(s: &str, radix: u32) -> LexResult { - // TODO(kdy1): unwrap -> Err - let c = char::from_u32( - u32::from_str_radix(s, radix).expect("failed to parse string as number"), - ) - .expect("failed to parse number as char"); - - Ok(c) - } - - fn is_hex(s: &str) -> bool { - s.chars().all(|c| c.is_ascii_hexdigit()) - } - - fn is_dec(s: &str) -> bool { - s.chars().all(|c| c.is_ascii_digit()) - } - - let mut s = CompactString::default(); - - debug_assert!(self.input().cur().is_some_and(|c| c == b'&')); - self.bump(); - - let start_pos = self.input().cur_pos(); - - for _ in 0..10 { - let c = match self.input().cur() { - Some(c) => c as char, - None => break, - }; - self.bump(); - - if c == ';' { - if let Some(stripped) = s.strip_prefix('#') { - if stripped.starts_with('x') { - if is_hex(&s[2..]) { - let value = from_code(&s[2..], 16)?; - - return Ok((value, format!("&{s};"))); - } - } else if is_dec(stripped) { - let value = from_code(stripped, 10)?; - - return Ok((value, format!("&{s};"))); - } - } else if let Some(entity) = xhtml(&s) { - return Ok((entity, format!("&{s};"))); - } - - break; - } - - s.push(c) - } - - unsafe { - // Safety: start_pos is a valid position because we got it from self.input - self.input_mut().reset_to(start_pos); - } - - Ok(('&', "&".to_string())) - } - - fn read_jsx_new_line(&mut self, normalize_crlf: bool) -> LexResult> { - debug_assert!(self.syntax().jsx()); - let ch = self.input().cur().unwrap() as char; - self.bump(); - - let out = if ch == '\r' && self.input().cur() == Some(b'\n') { - self.bump(); // `\n` - Either::Left(if normalize_crlf { "\n" } else { "\r\n" }) - } else { - Either::Right(ch) - }; - Ok(out) - } - - fn read_jsx_str(&mut self, quote: char) -> LexResult { - debug_assert!(self.syntax().jsx()); - let start = self.input().cur_pos(); - // Safety: cur() was Some(quote) - self.bump(); // `quote` - let mut out = String::new(); - let mut chunk_start = self.input().cur_pos(); - loop { - let ch = match self.input().cur() { - Some(c) => c as char, - None => { - self.emit_error(start, SyntaxError::UnterminatedStrLit); - break; - } - }; - let cur_pos = self.input().cur_pos(); - if ch == '\\' { - let value = unsafe { - // Safety: We already checked for the range - self.input_slice(chunk_start, cur_pos) - }; - - out.push_str(value); - out.push('\\'); - - self.bump(); - - chunk_start = self.input().cur_pos(); - - continue; - } - - if ch == quote { - break; - } - - if ch == '&' { - let value = unsafe { - // Safety: We already checked for the range - self.input_slice(chunk_start, cur_pos) - }; - - out.push_str(value); - - let jsx_entity = self.read_jsx_entity()?; - - out.push(jsx_entity.0); - - chunk_start = self.input().cur_pos(); - } else if ch.is_line_terminator() { - let value = unsafe { - // Safety: We already checked for the range - self.input_slice(chunk_start, cur_pos) - }; - - out.push_str(value); - - match self.read_jsx_new_line(false)? { - Either::Left(s) => { - out.push_str(s); - } - Either::Right(c) => { - out.push(c); - } - } - - chunk_start = cur_pos + BytePos(ch.len_utf8() as _); - } else { - // Safety: cur() was Some(ch) - self.bump(); - } - } - let cur_pos = self.input().cur_pos(); - let s = unsafe { - // Safety: We already checked for the range - self.input_slice(chunk_start, cur_pos) - }; - let value = if out.is_empty() { - // Fast path: We don't need to allocate - self.atom(s) - } else { - out.push_str(s); - self.atom(out) - }; - - // it might be at the end of the file when - // the string literal is unterminated - if self.input().peek_ahead().is_some() { - self.bump(); - } - - let end = self.input().cur_pos(); - let raw = unsafe { - // Safety: Both of `start` and `end` are generated from `cur_pos()` - self.input_slice(start, end) - }; - let raw = self.atom(raw); - Ok(Self::Token::str(value.into(), raw, self)) - } - - // Modified based on - /// Unicode code unit (`\uXXXX`). - /// - /// The opening `\u` must already have been consumed before calling this - /// method. - /// - /// See background info on surrogate pairs: - /// * `https://mathiasbynens.be/notes/javascript-encoding#surrogate-formulae` - /// * `https://mathiasbynens.be/notes/javascript-identifiers-es6` - fn read_unicode_code_unit(&mut self) -> LexResult> { - const MIN_HIGH: u32 = 0xd800; - const MAX_HIGH: u32 = 0xdbff; - const MIN_LOW: u32 = 0xdc00; - const MAX_LOW: u32 = 0xdfff; - - let Some(high) = self.read_int_u32::<16>(4)? else { - return Ok(None); - }; - if let Some(ch) = char::from_u32(high) { - return Ok(Some(UnicodeEscape::CodePoint(ch))); - } - - // The first code unit of a surrogate pair is always in the range from 0xD800 to - // 0xDBFF, and is called a high surrogate or a lead surrogate. - // Note: `high` must be >= `MIN_HIGH`, otherwise `char::from_u32` would have - // returned `Some`, and already exited. - debug_assert!(high >= MIN_HIGH); - let is_pair = high <= MAX_HIGH - && self.input().cur() == Some(b'\\') - && self.input().peek() == Some(b'u'); - if !is_pair { - return Ok(Some(UnicodeEscape::LoneSurrogate(high))); - } - - let before_second = self.input().cur_pos(); - - // Bump `\u` - unsafe { - self.input_mut().bump_bytes(2); - } - - let Some(low) = self.read_int_u32::<16>(4)? else { - return Ok(None); - }; - - // The second code unit of a surrogate pair is always in the range from 0xDC00 - // to 0xDFFF, and is called a low surrogate or a trail surrogate. - // If this isn't a valid pair, rewind to before the 2nd, and return the first - // only. The 2nd could be the first part of a valid pair. - if !(MIN_LOW..=MAX_LOW).contains(&low) { - unsafe { - // Safety: state is valid position because we got it from cur_pos() - self.input_mut().reset_to(before_second); - } - return Ok(Some(UnicodeEscape::LoneSurrogate(high))); - } - - let code_point = pair_to_code_point(high, low); - // SAFETY: `high` and `low` have been checked to be in ranges which always yield - // a `code_point` which is a valid `char` - let ch = unsafe { char::from_u32_unchecked(code_point) }; - Ok(Some(UnicodeEscape::SurrogatePair(ch))) - } - - fn read_unicode_escape(&mut self) -> LexResult { - debug_assert_eq!(self.cur(), Some(b'u')); - - let mut is_curly = false; - - self.bump(); // 'u' - - if self.eat(b'{') { - is_curly = true; - } - - let state = self.input().cur_pos(); - let c = match self.read_int_u32::<16>(if is_curly { 0 } else { 4 }) { - Ok(Some(val)) => { - if 0x0010_ffff >= val { - char::from_u32(val) - } else { - let start = self.cur_pos(); - - self.error( - start, - SyntaxError::BadCharacterEscapeSequence { - expected: if is_curly { - "1-6 hex characters in the range 0 to 10FFFF." - } else { - "4 hex characters" - }, - }, - )? - } - } - _ => { - let start = self.cur_pos(); - - self.error( - start, - SyntaxError::BadCharacterEscapeSequence { - expected: if is_curly { - "1-6 hex characters" - } else { - "4 hex characters" - }, - }, - )? - } - }; - - match c { - Some(c) => { - if is_curly && !self.eat(b'}') { - self.error(state, SyntaxError::InvalidUnicodeEscape)? - } - - Ok(UnicodeEscape::CodePoint(c)) - } - _ => { - unsafe { - // Safety: state is valid position because we got it from cur_pos() - self.input_mut().reset_to(state); - } - - let Some(value) = self.read_unicode_code_unit()? else { - self.error( - state, - SyntaxError::BadCharacterEscapeSequence { - expected: if is_curly { - "1-6 hex characters" - } else { - "4 hex characters" - }, - }, - )? - }; - - if is_curly && !self.eat(b'}') { - self.error(state, SyntaxError::InvalidUnicodeEscape)? - } - - Ok(value) - } - } - } - - #[cold] - fn read_shebang(&mut self) -> LexResult> { - if self.input().cur() != Some(b'#') || self.input().peek() != Some(b'!') { - return Ok(None); - } - self.bump(); // `#` - self.bump(); // `!` - let s = self.input_uncons_while(|c| !c.is_line_terminator()); - Ok(Some(self.atom(s))) - } - - fn read_tmpl_token(&mut self, start_of_tpl: BytePos) -> LexResult { - let start = self.cur_pos(); - - let mut cooked = Ok(Wtf8Buf::new()); - let mut cooked_slice_start = start; - let raw_slice_start = start; - - macro_rules! consume_cooked { - () => {{ - if let Ok(cooked) = &mut cooked { - let last_pos = self.cur_pos(); - cooked.push_str(unsafe { - // Safety: Both of start and last_pos are valid position because we got them - // from `self.input` - self.input_slice(cooked_slice_start, last_pos) - }); - } - }}; - } - - // Handle edge case for immediate template end - if start == self.cur_pos() && self.state().last_was_tpl_element() { - if let Some(c) = self.cur() { - if c == b'$' && self.peek() == Some(b'{') { - self.bump(); // '$' - self.bump(); // '{' - return Ok(Self::Token::DOLLAR_LBRACE); - } else if c == b'`' { - self.bump(); // '`' - return Ok(Self::Token::BACKQUOTE); - } - } - } - - // Fast path: use byte_search to scan for template literal terminators - loop { - let matched_byte = byte_search! { - lexer: self, - table: TEMPLATE_LITERAL_TABLE, - handle_eof: { - // EOF reached - unterminated template - self.error(start_of_tpl, SyntaxError::UnterminatedTpl)? - } - }; - - match matched_byte { - b'$' => { - // Check if this is ${ - if self.peek() == Some(b'{') { - // Found template substitution - let cooked = if cooked_slice_start == raw_slice_start { - let last_pos = self.cur_pos(); - let s = unsafe { - // Safety: Both of start and last_pos are valid position because we - // got them from `self.input` - self.input_slice(cooked_slice_start, last_pos) - }; - Ok(self.wtf8_atom(Wtf8::from_str(s))) - } else { - consume_cooked!(); - cooked.map(|s| self.wtf8_atom(&*s)) - }; - - let end = self.input().cur_pos(); - let raw = unsafe { - // Safety: Both of start and last_pos are valid position because we got - // them from `self.input` - self.input_slice(raw_slice_start, end) - }; - let raw = self.atom(raw); - return Ok(Self::Token::template(cooked, raw, self)); - } else { - // Just a regular $ character, continue scanning - self.bump(); - continue; - } - } - b'`' => { - // Found template end - let cooked = if cooked_slice_start == raw_slice_start { - let last_pos = self.cur_pos(); - let s = unsafe { self.input_slice(cooked_slice_start, last_pos) }; - Ok(self.wtf8_atom(Wtf8::from_str(s))) - } else { - consume_cooked!(); - cooked.map(|s| self.wtf8_atom(&*s)) - }; - - let end = self.input().cur_pos(); - let raw = unsafe { self.input_slice(raw_slice_start, end) }; - let raw = self.atom(raw); - return Ok(Self::Token::template(cooked, raw, self)); - } - b'\r' => { - // Handle carriage return line terminator - self.state_mut().mark_had_line_break(); - consume_cooked!(); - - // Handle carriage return - consume \r and optionally \n, normalize to \n - self.bump(); // '\r' - if self.peek() == Some(b'\n') { - self.bump(); // '\n' - } - - if let Ok(ref mut cooked) = cooked { - cooked.push_char('\n'); - } - cooked_slice_start = self.cur_pos(); - } - b'\\' => { - // Handle escape sequence - fall back to slow path for this part - consume_cooked!(); - - match self.read_escaped_char(true) { - Ok(Some(escaped)) => { - if let Ok(ref mut cooked) = cooked { - cooked.push(escaped); - } - } - Ok(None) => {} - Err(error) => { - cooked = Err(error); - } - } - - cooked_slice_start = self.cur_pos(); - } - _ => unreachable!(), - } - } - } - - /// Read an escaped character for string literal. - /// - /// In template literal, we should preserve raw string. - fn read_escaped_char(&mut self, in_template: bool) -> LexResult> { - debug_assert_eq!(self.cur(), Some(b'\\')); - - let start = self.cur_pos(); - - self.bump(); // '\' - - let c = match self.cur() { - Some(c) => c as char, - None => self.error_span(pos_span(start), SyntaxError::InvalidStrEscape)?, - }; - - let c = match c { - '\\' => '\\', - 'n' => '\n', - 'r' => '\r', - 't' => '\t', - 'b' => '\u{0008}', - 'v' => '\u{000b}', - 'f' => '\u{000c}', - '\r' => { - self.bump(); // remove '\r' - - self.eat(b'\n'); - - return Ok(None); - } - '\n' | '\u{2028}' | '\u{2029}' => { - self.bump(); - - return Ok(None); - } - - // read hexadecimal escape sequences - 'x' => { - self.bump(); // 'x' - - match self.read_int_u32::<16>(2)? { - Some(val) => return Ok(CodePoint::from_u32(val)), - None => self.error( - start, - SyntaxError::BadCharacterEscapeSequence { - expected: "2 hex characters", - }, - )?, - } - } - - // read unicode escape sequences - 'u' => match self.read_unicode_escape() { - Ok(value) => { - return Ok(Some(value.into())); - } - Err(err) => self.error(start, err.into_kind())?, - }, - - // octal escape sequences - '0'..='7' => { - self.bump(); - - let first_c = if c == '0' { - match self.cur() { - Some(next) if (next as char).is_digit(8) => c, - // \0 is not an octal literal nor decimal literal. - _ => return Ok(Some(CodePoint::from_char('\u{0000}'))), - } - } else { - c - }; - - // TODO: Show template instead of strict mode - if in_template { - self.error(start, SyntaxError::LegacyOctal)? - } - - self.emit_strict_mode_error(start, SyntaxError::LegacyOctal); - - let mut value: u8 = first_c.to_digit(8).unwrap() as u8; - - macro_rules! one { - ($check:expr) => {{ - let cur = self.cur(); - - match cur.and_then(|c| (c as char).to_digit(8)) { - Some(v) => { - value = if $check { - let new_val = value - .checked_mul(8) - .and_then(|value| value.checked_add(v as u8)); - match new_val { - Some(val) => val, - None => return Ok(CodePoint::from_u32(value as u32)), - } - } else { - value * 8 + v as u8 - }; - - self.bump(); - } - _ => return Ok(CodePoint::from_u32(value as u32)), - } - }}; - } - - one!(false); - one!(true); - - return Ok(CodePoint::from_u32(value as u32)); - } - _ => c, - }; - - // Safety: cur() is Some(c) if this method is called. - self.bump(); - - Ok(CodePoint::from_u32(c as u32)) - } - - /// Expects current char to be '/' - fn read_regexp(&mut self, start: BytePos) -> LexResult { - unsafe { - // Safety: start is valid position, and cur() is Some('/') - self.input_mut().reset_to(start); - } - - debug_assert_eq!(self.cur(), Some(b'/')); - - let start = self.cur_pos(); - - self.bump(); // bump '/' - - let slice_start = self.cur_pos(); - - let (mut escaped, mut in_class) = (false, false); - - while let Some(c) = self.cur() { - let c = c as char; - // This is ported from babel. - // Seems like regexp literal cannot contain linebreak. - if c.is_line_terminator() { - let span = self.span(start); - - return Err(crate::error::Error::new( - span, - SyntaxError::UnterminatedRegExp, - )); - } - - if escaped { - escaped = false; - } else { - match c { - '[' => in_class = true, - ']' if in_class => in_class = false, - // Terminates content part of regex literal - '/' if !in_class => break, - _ => {} - } - - escaped = c == '\\'; - } - - self.bump(); - } - - let content = { - let end = self.cur_pos(); - let s = unsafe { self.input_slice(slice_start, end) }; - self.atom(s) - }; - - // input is terminated without following `/` - if !self.is(b'/') { - let span = self.span(start); - - return Err(crate::error::Error::new( - span, - SyntaxError::UnterminatedRegExp, - )); - } - - self.bump(); // '/' - - // Spec says "It is a Syntax Error if IdentifierPart contains a Unicode escape - // sequence." TODO: check for escape - - // Need to use `read_word` because '\uXXXX' sequences are allowed - // here (don't ask). - // let flags_start = self.cur_pos(); - let flags = { - match self.cur() { - Some(c) if c.is_ident_start() => self - .read_word_as_str_with() - .map(|(s, _)| Some(self.atom(s))), - _ => Ok(None), - } - }? - .unwrap_or_default(); - - Ok(Self::Token::regexp(content, flags, self)) - } - - /// This method is optimized for texts without escape sequences. - fn read_word_as_str_with(&mut self) -> LexResult<(Cow<'a, str>, bool)> { - debug_assert!(self.cur().is_some()); - let slice_start = self.cur_pos(); - - // Fast path: try to scan ASCII identifier using byte_search - if let Some(c) = self.input().cur_as_ascii() { - if Ident::is_valid_ascii_start(c) { - // Advance past first byte - self.bump(); - - // Use byte_search to quickly scan to end of ASCII identifier - let next_byte = byte_search! { - lexer: self, - table: NOT_ASCII_ID_CONTINUE_TABLE, - handle_eof: { - // Reached EOF, entire remainder is identifier - let end = self.cur_pos(); - let s = unsafe { - // Safety: slice_start and end are valid position because we got them from - // `self.input` - self.input_slice(slice_start, end) - }; - - return Ok((Cow::Borrowed(s), false)); - }, - }; - - // Check if we hit end of identifier or need to fall back to slow path - if !next_byte.is_ascii() { - // Hit Unicode character, fall back to slow path from current position - return self.read_word_as_str_with_slow_path(slice_start); - } else if next_byte == b'\\' { - // Hit escape sequence, fall back to slow path from current position - return self.read_word_as_str_with_slow_path(slice_start); - } else { - // Hit end of identifier (non-continue ASCII char) - let end = self.cur_pos(); - let s = unsafe { - // Safety: slice_start and end are valid position because we got them from - // `self.input` - self.input_slice(slice_start, end) - }; - - return Ok((Cow::Borrowed(s), false)); - } - } - } - - // Fall back to slow path for non-ASCII start or complex cases - self.read_word_as_str_with_slow_path(slice_start) - } - - /// Slow path for identifier parsing that handles Unicode and escapes - #[cold] - fn read_word_as_str_with_slow_path( - &mut self, - mut slice_start: BytePos, - ) -> LexResult<(Cow<'a, str>, bool)> { - let mut first = true; - let mut has_escape = false; - - let mut buf = String::with_capacity(16); - loop { - if let Some(c) = self.input().cur_as_ascii() { - if Ident::is_valid_ascii_continue(c) { - self.bump(); - continue; - } else if first && Ident::is_valid_ascii_start(c) { - self.bump(); - first = false; - continue; - } - - // unicode escape - if c == b'\\' { - first = false; - has_escape = true; - let start = self.cur_pos(); - self.bump(); - - if !self.is(b'u') { - self.error_span(pos_span(start), SyntaxError::ExpectedUnicodeEscape)? - } - - { - let end = self.input().cur_pos(); - let s = unsafe { - // Safety: start and end are valid position because we got them from - // `self.input` - self.input_slice(slice_start, start) - }; - buf.push_str(s); - unsafe { - // Safety: We got end from `self.input` - self.input_mut().reset_to(end); - } - } - - let value = self.read_unicode_escape()?; - - match value { - UnicodeEscape::CodePoint(ch) => { - let valid = if first { - ch.is_ident_start() - } else { - ch.is_ident_part() - }; - if !valid { - self.emit_error(start, SyntaxError::InvalidIdentChar); - } - buf.push(ch); - } - UnicodeEscape::SurrogatePair(ch) => { - buf.push(ch); - self.emit_error(start, SyntaxError::InvalidIdentChar); - } - UnicodeEscape::LoneSurrogate(code_point) => { - buf.push_str(format!("\\u{code_point:04X}").as_str()); - self.emit_error(start, SyntaxError::InvalidIdentChar); - } - }; - - slice_start = self.cur_pos(); - continue; - } - - // ASCII but not a valid identifier - break; - } else if let Some(c) = self.input().cur_as_char() { - if Ident::is_valid_non_ascii_continue(c) { - self.bump(); - continue; - } else if first && Ident::is_valid_non_ascii_start(c) { - self.bump(); - first = false; - continue; - } - } - - break; - } - - let end = self.cur_pos(); - let s = unsafe { - // Safety: slice_start and end are valid position because we got them from - // `self.input` - self.input_slice(slice_start, end) - }; - let value = if !has_escape { - // Fast path: raw slice is enough if there's no escape. - Cow::Borrowed(s) - } else { - buf.push_str(s); - Cow::Owned(buf) - }; - - Ok((value, has_escape)) - } - - /// `#` - fn read_token_number_sign(&mut self) -> LexResult { - debug_assert!(self.cur().is_some_and(|c| c == b'#')); - - self.bump(); // '#' - - // `#` can also be a part of shebangs, however they should have been - // handled by `read_shebang()` - debug_assert!( - !self.input().is_at_start() || self.cur() != Some(b'!'), - "#! should have already been handled by read_shebang()" - ); - Ok(Self::Token::HASH) - } - - /// Read a token given `.`. - /// - /// This is extracted as a method to reduce size of `read_token`. - #[inline(never)] - fn read_token_dot(&mut self) -> LexResult { - debug_assert!(self.cur().is_some_and(|c| c == b'.')); - // Check for eof - let next = match self.input().peek() { - Some(next) => next, - None => { - self.bump(); // '.' - return Ok(Self::Token::DOT); - } - }; - if next.is_ascii_digit() { - return self.read_number::().map(|v| match v { - Left((value, raw)) => Self::Token::num(value, raw, self), - Right(_) => unreachable!("read_number should not return bigint for leading dot"), - }); - } - - self.bump(); // 1st `.` - - if next == b'.' && self.input().peek() == Some(b'.') { - self.bump(); // 2nd `.` - self.bump(); // 3rd `.` - - return Ok(Self::Token::DOTDOTDOT); - } - - Ok(Self::Token::DOT) - } - - /// Read a token given `?`. - /// - /// This is extracted as a method to reduce size of `read_token`. - #[inline(never)] - fn read_token_question_mark(&mut self) -> LexResult { - debug_assert!(self.cur().is_some_and(|c| c == b'?')); - self.bump(); - // Safety: b'?' and b'=' are ASCII. - if unsafe { self.input_mut().eat_byte(b'?') } { - if unsafe { self.input_mut().eat_byte(b'=') } { - Ok(Self::Token::NULLISH_ASSIGN) - } else { - Ok(Self::Token::NULLISH_COALESCING) - } - } else { - Ok(Self::Token::QUESTION) - } - } - - /// Read a token given `:`. - /// - /// This is extracted as a method to reduce size of `read_token`. - #[inline(never)] - fn read_token_colon(&mut self) -> LexResult { - debug_assert!(self.cur().is_some_and(|c| c == b':')); - self.bump(); // ':' - Ok(Self::Token::COLON) - } - - /// Read a token given `0`. - /// - /// This is extracted as a method to reduce size of `read_token`. - #[inline(never)] - fn read_token_zero(&mut self) -> LexResult { - debug_assert_eq!(self.cur(), Some(b'0')); - let next = self.input().peek(); - - let bigint = match next { - Some(b'x') | Some(b'X') => self.read_radix_number::<16>(), - Some(b'o') | Some(b'O') => self.read_radix_number::<8>(), - Some(b'b') | Some(b'B') => self.read_radix_number::<2>(), - _ => { - return self.read_number::().map(|v| match v { - Left((value, raw)) => Self::Token::num(value, raw, self), - Right((value, raw)) => Self::Token::bigint(value, raw, self), - }); - } - }; - - bigint.map(|v| match v { - Left((value, raw)) => Self::Token::num(value, raw, self), - Right((value, raw)) => Self::Token::bigint(value, raw, self), - }) - } - - /// Read a token given `|` or `&`. - /// - /// This is extracted as a method to reduce size of `read_token`. - #[inline(never)] - fn read_token_logical(&mut self) -> LexResult { - debug_assert!(C == b'|' || C == b'&'); - let is_bit_and = C == b'&'; - let had_line_break_before_last = self.had_line_break_before_last(); - let start = self.cur_pos(); - - // Safety: cur() is Some(c as char) - self.bump(); - let token = if is_bit_and { - Self::Token::BIT_AND - } else { - Self::Token::BIT_OR - }; - - // '|=', '&=' - // Safety: b'=' is ASCII. - if unsafe { self.input_mut().eat_byte(b'=') } { - return Ok(if is_bit_and { - Self::Token::BIT_AND_EQ - } else { - debug_assert!(token.is_bit_or()); - Self::Token::BIT_OR_EQ - }); - } - - // '||', '&&' - if self.input().cur() == Some(C) { - // Safety: cur() is Some(c) - self.bump(); - - if self.input().cur() == Some(b'=') { - // Safety: cur() is Some('=') - self.bump(); - - return Ok(if is_bit_and { - Self::Token::LOGICAL_AND_EQ - } else { - debug_assert!(token.is_bit_or()); - Self::Token::LOGICAL_OR_EQ - }); - } - - // ||||||| - // ^ - if had_line_break_before_last && !is_bit_and && self.is_str("||||| ") { - let span = fixed_len_span(start, 7); - self.emit_error_span(span, SyntaxError::TS1185); - self.skip_line_comment(5); - self.skip_space::(); - return self.error_span(span, SyntaxError::TS1185); - } - - return Ok(if is_bit_and { - Self::Token::LOGICAL_AND - } else { - debug_assert!(token.is_bit_or()); - Self::Token::LOGICAL_OR - }); - } - - Ok(token) - } - - /// Read a token given `*` or `%`. - /// - /// This is extracted as a method to reduce size of `read_token`. - #[inline(never)] - fn read_token_mul_mod(&mut self, is_mul: bool) -> LexResult { - debug_assert!(self.cur().is_some_and(|c| c == b'*' || c == b'%')); - self.bump(); - let token = if is_mul { - // Safety: b'*' is ASCII. - if unsafe { self.input_mut().eat_byte(b'*') } { - // `**` - Self::Token::EXP - } else { - Self::Token::MUL - } - } else { - Self::Token::MOD - }; - - // Safety: b'=' is ASCII. - Ok(if unsafe { self.input_mut().eat_byte(b'=') } { - if token.is_star() { - Self::Token::MUL_EQ - } else if token.is_mod() { - Self::Token::MOD_EQ - } else { - debug_assert!(token.is_exp()); - Self::Token::EXP_EQ - } - } else { - token - }) - } - - #[inline(never)] - fn read_slash(&mut self) -> LexResult { - debug_assert_eq!(self.cur(), Some(b'/')); - self.bump(); // '/' - Ok(if self.eat(b'=') { - Self::Token::DIV_EQ - } else { - Self::Token::DIV - }) - } - - /// This can be used if there's no keyword starting with the first - /// character. - fn read_ident_unknown(&mut self) -> LexResult { - debug_assert!(self.cur().is_some()); - - let (s, has_escape) = self.read_word_as_str_with()?; - let atom = self.atom(s); - let word = Self::Token::unknown_ident(atom, self); - - if has_escape { - self.update_token_flags(|flags| *flags |= TokenFlags::UNICODE); - } - - Ok(word) - } - - /// See https://tc39.github.io/ecma262/#sec-literals-string-literals - // TODO: merge `read_str_lit` and `read_jsx_str` - fn read_str_lit(&mut self) -> LexResult { - debug_assert!(self.cur() == Some(b'\'') || self.cur() == Some(b'"')); - let start = self.cur_pos(); - let quote = self.cur().unwrap(); - - self.bump(); // '"' or '\'' - - let mut slice_start = self.input().cur_pos(); - - let mut buf: Option = None; - - loop { - let table = if quote == b'"' { - &DOUBLE_QUOTE_STRING_END_TABLE - } else { - &SINGLE_QUOTE_STRING_END_TABLE - }; - - let fast_path_result = byte_search! { - lexer: self, - table: table, - handle_eof: { - let value_end = self.cur_pos(); - let s = unsafe { - // Safety: slice_start and value_end are valid position because we - // got them from `self.input` - self.input_slice(slice_start, value_end) - }; - - self.emit_error(start, SyntaxError::UnterminatedStrLit); - - let end = self.cur_pos(); - let raw = unsafe { self.input_slice(start, end) }; - return Ok(Self::Token::str(self.wtf8_atom(Wtf8::from_str(s)), self.atom(raw), self)); - }, - }; - // dbg!(char::from_u32(fast_path_result as u32)); - - match fast_path_result { - b'"' | b'\'' if fast_path_result == quote => { - let value_end = self.cur_pos(); - - let value = if let Some(buf) = buf.as_mut() { - // `buf` only exist when there has escape. - debug_assert!(unsafe { self.input_slice(start, value_end).contains('\\') }); - let s = unsafe { - // Safety: slice_start and value_end are valid position because we - // got them from `self.input` - self.input_slice(slice_start, value_end) - }; - buf.push_str(s); - self.wtf8_atom(&**buf) - } else { - let s = unsafe { self.input_slice(slice_start, value_end) }; - self.wtf8_atom(Wtf8::from_str(s)) - }; - - // Safety: cur is quote - self.bump(); - - let end = self.cur_pos(); - let raw = unsafe { - // Safety: start and end are valid position because we got them from - // `self.input` - self.input_slice(start, end) - }; - let raw = self.atom(raw); - return Ok(Self::Token::str(value, raw, self)); - } - b'\\' => { - let end = self.cur_pos(); - let s = unsafe { - // Safety: start and end are valid position because we got them from - // `self.input` - self.input_slice(slice_start, end) - }; - - if buf.is_none() { - buf = Some(Wtf8Buf::from_str(s)); - } else { - buf.as_mut().unwrap().push_str(s); - } - - if let Some(escaped) = self.read_escaped_char(false)? { - buf.as_mut().unwrap().push(escaped); - } - - slice_start = self.cur_pos(); - continue; - } - b'\n' | b'\r' => { - let end = self.cur_pos(); - let s = unsafe { - // Safety: start and end are valid position because we got them from - // `self.input` - self.input_slice(slice_start, end) - }; - - self.emit_error(start, SyntaxError::UnterminatedStrLit); - - let end = self.cur_pos(); - - let raw = unsafe { - // Safety: start and end are valid position because we got them from - // `self.input` - self.input_slice(start, end) - }; - return Ok(Self::Token::str( - self.wtf8_atom(Wtf8::from_str(s)), - self.atom(raw), - self, - )); - } - _ => self.bump(), - } - } - } - - fn read_keyword_with( - &mut self, - convert: &dyn Fn(&str) -> Option, - ) -> LexResult { - debug_assert!(self.cur().is_some()); - - let start = self.cur_pos(); - let (s, has_escape) = self.read_keyword_as_str_with()?; - if let Some(word) = convert(s.as_ref()) { - // Note: ctx is store in lexer because of this error. - // 'await' and 'yield' may have semantic of reserved word, which means lexer - // should know context or parser should handle this error. Our approach to this - // problem is former one. - if has_escape && word.is_reserved(self.ctx()) { - self.error( - start, - SyntaxError::EscapeInReservedWord { word: Atom::new(s) }, - ) - } else { - Ok(word) - } - } else { - let atom = self.atom(s); - Ok(Self::Token::unknown_ident(atom, self)) - } - } - - /// This is a performant version of [Lexer::read_word_as_str_with] for - /// reading keywords. We should make sure the first byte is a valid - /// ASCII. - fn read_keyword_as_str_with(&mut self) -> LexResult<(Cow<'a, str>, bool)> { - let slice_start = self.cur_pos(); - - // Fast path: try to scan ASCII identifier using byte_search - // Performance optimization: check if first char disqualifies as keyword - // Advance past first byte - self.bump(); - - // Use byte_search to quickly scan to end of ASCII identifier - let next_byte = byte_search! { - lexer: self, - table: NOT_ASCII_ID_CONTINUE_TABLE, - handle_eof: { - // Reached EOF, entire remainder is identifier - let end = self.cur_pos(); - let s = unsafe { - // Safety: slice_start and end are valid position because we got them from - // `self.input` - self.input_slice(slice_start, end) - }; - - return Ok((Cow::Borrowed(s), false)); - }, - }; - - // Check if we hit end of identifier or need to fall back to slow path - if !next_byte.is_ascii() || next_byte == b'\\' { - // Hit Unicode character or escape sequence, fall back to slow path from current - // position - self.read_word_as_str_with_slow_path(slice_start) - } else { - // Hit end of identifier (non-continue ASCII char) - let end = self.cur_pos(); - let s = unsafe { - // Safety: slice_start and end are valid position because we got them from - // `self.input` - self.input_slice(slice_start, end) - }; - - Ok((Cow::Borrowed(s), false)) - } - } -} - -pub fn pos_span(p: BytePos) -> Span { - Span::new_with_checked(p, p) -} - -pub fn fixed_len_span(p: BytePos, len: u32) -> Span { - Span::new_with_checked(p, p + BytePos(len)) -} diff --git a/crates/swc_ecma_lexer/src/common/lexer/number.rs b/crates/swc_ecma_lexer/src/common/lexer/number.rs deleted file mode 100644 index 604c9d7cdfce..000000000000 --- a/crates/swc_ecma_lexer/src/common/lexer/number.rs +++ /dev/null @@ -1,94 +0,0 @@ -use swc_common::BytePos; - -pub struct LazyInteger { - pub(super) start: BytePos, - pub(super) end: BytePos, - /// `true` if there was `8` or `9`` - pub(super) not_octal: bool, - pub(super) has_underscore: bool, -} - -const MAX_SAFE_INT: u64 = 9007199254740991; - -pub(super) fn parse_integer(s: &str) -> f64 { - debug_assert!(matches!(RADIX, 2 | 8 | 10 | 16)); - debug_assert!(!s.is_empty()); - debug_assert!(!s.contains('_')); - - if RADIX == 10 { - parse_integer_from_dec(s) - } else if RADIX == 16 { - parse_integer_from_hex(s) - } else if RADIX == 2 { - parse_integer_from_bin(s) - } else if RADIX == 8 { - parse_integer_from_oct(s) - } else { - unreachable!() - } -} - -fn parse_integer_from_hex(s: &str) -> f64 { - debug_assert!(s.chars().all(|c| c.is_ascii_hexdigit())); - const MAX_FAST_INT_LEN: usize = MAX_SAFE_INT.ilog(16) as usize; - if s.len() > MAX_FAST_INT_LEN { - // Hex digit character representations: - // b'0'==0x30, b'1'==0x31 ... b'9'==0x39 → low nibble: 0x0-0x9 - // b'A'==0x41, b'B'==0x42 ... b'F'==0x46 → low nibble: 0x1-0x6 - // b'a'==0x61, b'b'==0x62 ... b'f'==0x66 → low nibble: 0x1-0x6 - // - // Conversion requires only the low 4 bits: - // digit_char & 0x0F gives base value: - // - For '0'-'9': direct value (0-9) - // - For 'A'-'F'/'a'-'f': offset base (1-6) - // Add 9 for alphabetic chars: (low_nibble + 9) → 0xA-0xF - // - // Example: (b'A' & 0x0f) + 9 == 0x1 + 9 == 0xA - s.as_bytes().iter().fold(0f64, |res, &cur| { - res.mul_add( - 16., - if cur < b'A' { - cur & 0xf - } else { - (cur & 0xf) + 9 - } as f64, - ) - }) - } else { - u64::from_str_radix(s, 16).unwrap() as f64 - } -} - -fn parse_integer_from_bin(s: &str) -> f64 { - debug_assert!(s.chars().all(|c| c == '0' || c == '1')); - const MAX_FAST_INT_LEN: usize = MAX_SAFE_INT.ilog2() as usize; - if s.len() > MAX_FAST_INT_LEN { - s.as_bytes().iter().fold(0f64, |res, &cur| { - res.mul_add(2., if cur == b'0' { 0. } else { 1. }) - }) - } else { - u64::from_str_radix(s, 2).unwrap() as f64 - } -} - -fn parse_integer_from_oct(s: &str) -> f64 { - debug_assert!(s.chars().all(|c| matches!(c, '0'..='7'))); - const MAX_FAST_INT_LEN: usize = MAX_SAFE_INT.ilog(8) as usize; - if s.len() > MAX_FAST_INT_LEN { - s.as_bytes() - .iter() - .fold(0f64, |res, &cur| res.mul_add(8., (cur - b'0') as f64)) - } else { - u64::from_str_radix(s, 8).unwrap() as f64 - } -} - -fn parse_integer_from_dec(s: &str) -> f64 { - debug_assert!(s.chars().all(|c| c.is_ascii_digit())); - const MAX_FAST_INT_LEN: usize = MAX_SAFE_INT.ilog10() as usize; - if s.len() > MAX_FAST_INT_LEN { - s.parse::().unwrap() - } else { - s.parse::().unwrap() as f64 - } -} diff --git a/crates/swc_ecma_lexer/src/common/lexer/search.rs b/crates/swc_ecma_lexer/src/common/lexer/search.rs deleted file mode 100644 index 356ea44cc306..000000000000 --- a/crates/swc_ecma_lexer/src/common/lexer/search.rs +++ /dev/null @@ -1,146 +0,0 @@ -//! Utilities inspired by OXC lexer for fast byte-wise searching over source -//! text. - -/// How many bytes we process per batch when scanning. -pub const SEARCH_BATCH_SIZE: usize = 32; - -/// Compile-time lookup table guaranteeing UTF-8 boundary safety. -#[repr(C, align(64))] -pub struct SafeByteMatchTable([bool; 256]); - -impl SafeByteMatchTable { - pub const fn new(bytes: [bool; 256]) -> Self { - // Safety guarantee: either all leading bytes (0xC0..0xF7) match, or all - // continuation bytes (0x80..0xBF) *do not* match. This ensures that if - // we stop on a match the input cursor is on a UTF-8 char boundary. - let mut unicode_start_all_match = true; - let mut unicode_cont_all_no_match = true; - let mut i = 0; - while i < 256 { - let m = bytes[i]; - if m { - if i >= 0x80 && i < 0xc0 { - unicode_cont_all_no_match = false; - } - } else if i >= 0xc0 && i < 0xf8 { - unicode_start_all_match = false; - } - i += 1; - } - assert!( - unicode_start_all_match || unicode_cont_all_no_match, - "Cannot create SafeByteMatchTable with an unsafe pattern" - ); - Self(bytes) - } - - #[inline] - pub const fn use_table(&self) {} - - #[inline] - pub const fn matches(&self, b: u8) -> bool { - self.0[b as usize] - } -} - -// ------------------------- Macros ------------------------- - -#[macro_export] -macro_rules! safe_byte_match_table { - (|$byte:ident| $body:expr) => {{ - use $crate::common::lexer::search::SafeByteMatchTable; - #[allow(clippy::eq_op, clippy::allow_attributes)] - const TABLE: SafeByteMatchTable = seq_macro::seq!($byte in 0u8..=255 { - SafeByteMatchTable::new([#($body,)*]) - }); - TABLE - }}; -} - -#[macro_export] -macro_rules! byte_search { - // Simple version without continue_if - ( - lexer: $lexer:ident, - table: $table:ident, - handle_eof: $eof_handler:expr $(,)? - ) => { - byte_search! { - lexer: $lexer, - table: $table, - continue_if: (_byte, _pos) false, - handle_eof: $eof_handler, - } - }; - - // Full version with continue_if support - ( - lexer: $lexer:ident, - table: $table:ident, - continue_if: ($byte:ident, $pos:ident) $should_continue:expr, - handle_eof: $eof_handler:expr $(,)? - ) => {{ - $table.use_table(); - loop { - // Open a new scope so the immutable borrow (slice/bytes) ends before we - // call `bump_bytes`, which requires `&mut`. - let (found_idx, $byte) = { - let slice = $lexer.input().as_str(); - if slice.is_empty() { - $eof_handler - } - let bytes = slice.as_bytes(); - let mut idx = 0usize; - let len = bytes.len(); - let mut found: Option<(usize, u8)> = None; - while idx < len { - let end = (idx + $crate::common::lexer::search::SEARCH_BATCH_SIZE).min(len); - let mut i = idx; - while i < end { - let b = bytes[i]; - if $table.matches(b) { - found = Some((i, b)); - break; - } - i += 1; - } - if found.is_some() { - break; - } - idx = end; - } - match found { - Some((i, b)) => (Some(i), b), - None => (None, 0), - } - }; // immutable borrow ends here - - match found_idx { - Some(i) => { - // Check if we should continue searching - let $pos = i; // Index within current slice - if $should_continue { - // Continue searching from next position - unsafe { - $lexer.input_mut().bump_bytes(i + 1); - } - continue; - } else { - unsafe { - $lexer.input_mut().bump_bytes(i); - } - break $byte; - } - } - None => { - // Consume remainder then run handler. - let len = $lexer.input().as_str().len(); - unsafe { - $lexer.input_mut().bump_bytes(len); - } - $eof_handler - } - } - } - }}; -} diff --git a/crates/swc_ecma_lexer/src/common/lexer/state.rs b/crates/swc_ecma_lexer/src/common/lexer/state.rs deleted file mode 100644 index c173d897933d..000000000000 --- a/crates/swc_ecma_lexer/src/common/lexer/state.rs +++ /dev/null @@ -1,83 +0,0 @@ -use swc_common::BytePos; - -use crate::common::syntax::SyntaxFlags; - -pub trait TokenType: TokenKind { - fn is_other_and_can_have_trailing_comment(self) -> bool; - fn is_other_and_before_expr_is_false(self) -> bool; -} - -pub trait TokenKind: Copy { - fn is_dot(self) -> bool; - fn is_bin_op(self) -> bool; - fn is_semi(self) -> bool; - fn is_template(self) -> bool; - fn is_keyword(self) -> bool; - fn is_colon(self) -> bool; - fn is_lbrace(self) -> bool; - fn is_rbrace(self) -> bool; - fn is_lparen(self) -> bool; - fn is_rparen(self) -> bool; - fn is_keyword_fn(self) -> bool; - fn is_keyword_return(self) -> bool; - fn is_keyword_yield(self) -> bool; - fn is_keyword_else(self) -> bool; - fn is_keyword_class(self) -> bool; - fn is_keyword_let(self) -> bool; - fn is_keyword_var(self) -> bool; - fn is_keyword_const(self) -> bool; - fn is_keyword_if(self) -> bool; - fn is_keyword_while(self) -> bool; - fn is_keyword_for(self) -> bool; - fn is_keyword_with(self) -> bool; - fn is_lt(self) -> bool; - fn is_gt(self) -> bool; - fn is_arrow(self) -> bool; - fn is_ident(self) -> bool; - fn is_known_ident_of(self) -> bool; - fn is_slash(self) -> bool; - fn is_dollar_lbrace(self) -> bool; - fn is_plus_plus(self) -> bool; - fn is_minus_minus(self) -> bool; - fn is_back_quote(self) -> bool; - fn is_jsx_tag_start(self) -> bool; - fn is_jsx_tag_end(self) -> bool; - fn before_expr(self) -> bool; -} - -pub trait State: Clone { - type TokenKind: std::fmt::Debug + Copy + TokenKind + Into; - type TokenType: std::fmt::Debug + Copy + TokenType; - - fn is_expr_allowed(&self) -> bool; - fn set_is_expr_allowed(&mut self, is_expr_allowed: bool); - fn set_next_regexp(&mut self, start: Option); - fn had_line_break(&self) -> bool; - fn mark_had_line_break(&mut self); - fn had_line_break_before_last(&self) -> bool; - fn token_contexts(&self) -> &crate::TokenContexts; - fn mut_token_contexts(&mut self) -> &mut crate::TokenContexts; - fn set_token_type(&mut self, token_type: Self::TokenType); - fn token_type(&self) -> Option; - fn syntax(&self) -> SyntaxFlags; - fn prev_hi(&self) -> BytePos; - fn start(&self) -> BytePos; - - fn can_have_trailing_line_comment(&self) -> bool { - let Some(t) = self.token_type() else { - return true; - }; - !t.is_bin_op() - } - - fn can_have_trailing_comment(&self) -> bool { - self.token_type().is_some_and(|t| { - !t.is_keyword() - && (t.is_semi() || t.is_lbrace() || t.is_other_and_can_have_trailing_comment()) - }) - } - - fn last_was_tpl_element(&self) -> bool { - self.token_type().is_some_and(|t| t.is_template()) - } -} diff --git a/crates/swc_ecma_lexer/src/common/lexer/token.rs b/crates/swc_ecma_lexer/src/common/lexer/token.rs deleted file mode 100644 index 392d68353033..000000000000 --- a/crates/swc_ecma_lexer/src/common/lexer/token.rs +++ /dev/null @@ -1,596 +0,0 @@ -use num_bigint::BigInt; -use swc_atoms::{Atom, Wtf8Atom}; -use swc_ecma_ast::{AssignOp, BinaryOp}; - -use super::LexResult; -use crate::common::{context::Context, input::Tokens}; - -pub trait TokenFactory<'a, TokenAndSpan, I: Tokens>: Sized + PartialEq { - type Lexer: super::Lexer<'a, TokenAndSpan>; - type Buffer: crate::common::parser::buffer::Buffer< - 'a, - I = I, - Token = Self, - TokenAndSpan = TokenAndSpan, - >; - - const FROM: Self; - const FOR: Self; - const INSTANCEOF: Self; - const SATISFIES: Self; - const THROW: Self; - const AS: Self; - const NAMESPACE: Self; - const RETURN: Self; - const AT: Self; - const EXPORT: Self; - const DECLARE: Self; - const ASSERTS: Self; - const ASSERT: Self; - const JSX_TAG_END: Self; - const JSX_TAG_START: Self; - const DOLLAR_LBRACE: Self; - const BACKQUOTE: Self; - const HASH: Self; - const IN: Self; - const IS: Self; - const CONST: Self; - const DOT: Self; - const TARGET: Self; - const GET: Self; - const SET: Self; - const DOTDOTDOT: Self; - const NULLISH_ASSIGN: Self; - const NULLISH_COALESCING: Self; - const QUESTION: Self; - const COLON: Self; - const COMMA: Self; - const BIT_AND: Self; - const BIT_AND_EQ: Self; - const BIT_OR: Self; - const BIT_OR_EQ: Self; - const LOGICAL_AND: Self; - const LOGICAL_AND_EQ: Self; - const LOGICAL_OR: Self; - const LOGICAL_OR_EQ: Self; - const MUL: Self; - const MUL_EQ: Self; - const MOD: Self; - const MOD_EQ: Self; - const EXP: Self; - const EXP_EQ: Self; - const DIV: Self; - const DIV_EQ: Self; - const EQUAL: Self; - const LSHIFT: Self; - const LSHIFT_EQ: Self; - const LESS: Self; - const GLOBAL: Self; - const LESS_EQ: Self; - const RSHIFT: Self; - const RSHIFT_EQ: Self; - const GREATER: Self; - const GREATER_EQ: Self; - const ZERO_FILL_RSHIFT: Self; - const ZERO_FILL_RSHIFT_EQ: Self; - const NULL: Self; - const ANY: Self; - const BOOLEAN: Self; - const BIGINT: Self; - const NEVER: Self; - const NUMBER: Self; - const OBJECT: Self; - const STRING: Self; - const SYMBOL: Self; - const UNKNOWN: Self; - const UNDEFINED: Self; - const INTRINSIC: Self; - const TRUE: Self; - const TRY: Self; - const FALSE: Self; - const ENUM: Self; - const YIELD: Self; - const LET: Self; - const VAR: Self; - const STATIC: Self; - const IMPLEMENTS: Self; - const INTERFACE: Self; - const TYPE: Self; - const PACKAGE: Self; - const PRIVATE: Self; - const PROTECTED: Self; - const PUBLIC: Self; - const READONLY: Self; - const ARROW: Self; - const REQUIRE: Self; - const AWAIT: Self; - const BREAK: Self; - const CONTINUE: Self; - const THIS: Self; - const SUPER: Self; - const WHILE: Self; - const DO: Self; - const LPAREN: Self; - const RPAREN: Self; - const LBRACKET: Self; - const RBRACKET: Self; - const LBRACE: Self; - const FINALLY: Self; - const CATCH: Self; - const SWITCH: Self; - const RBRACE: Self; - const FUNCTION: Self; - const IF: Self; - const ELSE: Self; - const CLASS: Self; - const NEW: Self; - const ABSTRACT: Self; - const ACCESSOR: Self; - const IMPORT: Self; - const PLUS: Self; - const MINUS: Self; - const BANG: Self; - const TILDE: Self; - const PLUS_PLUS: Self; - const MINUS_MINUS: Self; - const DELETE: Self; - const TYPEOF: Self; - const VOID: Self; - const EXTENDS: Self; - const SEMI: Self; - const OF: Self; - const KEYOF: Self; - const UNIQUE: Self; - const INFER: Self; - const USING: Self; - const WITH: Self; - const ASYNC: Self; - const CASE: Self; - const DEFAULT: Self; - const DEBUGGER: Self; - const EOF: Self; - - fn jsx_name(name: &'a str, lexer: &mut Self::Lexer) -> Self; - fn is_jsx_name(&self) -> bool; - fn take_jsx_name(self, buffer: &mut Self::Buffer) -> Atom; - - fn str(value: Wtf8Atom, raw: Atom, lexer: &mut Self::Lexer) -> Self; - fn is_str(&self) -> bool; - fn is_str_raw_content(&self, content: &str, buffer: &Self::Buffer) -> bool; - fn take_str(self, buffer: &mut Self::Buffer) -> (Wtf8Atom, Atom); - - fn template(cooked: LexResult, raw: Atom, lexer: &mut Self::Lexer) -> Self; - fn is_template(&self) -> bool; - fn take_template(self, buffer: &mut Self::Buffer) -> (LexResult, Atom); - - fn jsx_text(value: Atom, raw: Atom, lexer: &mut Self::Lexer) -> Self; - fn is_jsx_text(&self) -> bool; - fn take_jsx_text(self, buffer: &mut Self::Buffer) -> (Atom, Atom); - - fn regexp(content: Atom, flags: Atom, lexer: &mut Self::Lexer) -> Self; - fn is_regexp(&self) -> bool; - fn take_regexp(self, buffer: &mut Self::Buffer) -> (Atom, Atom); - - fn num(value: f64, raw: Atom, lexer: &mut Self::Lexer) -> Self; - fn is_num(&self) -> bool; - fn take_num(self, buffer: &mut Self::Buffer) -> (f64, Atom); - - fn bigint(value: Box, raw: Atom, lexer: &mut Self::Lexer) -> Self; - fn is_bigint(&self) -> bool; - fn take_bigint(self, buffer: &mut Self::Buffer) -> (Box, Atom); - - fn shebang(value: Atom, lexer: &mut Self::Lexer) -> Self; - fn is_shebang(&self) -> bool; - fn take_shebang(self, buffer: &mut Self::Buffer) -> Atom; - - fn unknown_ident(value: Atom, lexer: &mut Self::Lexer) -> Self; - fn is_unknown_ident(&self) -> bool; - fn take_unknown_ident(self, buffer: &mut Self::Buffer) -> Atom; - fn take_unknown_ident_ref<'b>(&'b self, buffer: &'b Self::Buffer) -> &'b Atom; - - fn is_known_ident(&self) -> bool; - fn take_known_ident(&self) -> Atom; - - fn starts_expr(&self) -> bool; - fn to_string(&self, buffer: &Self::Buffer) -> String; - - fn is_error(&self) -> bool; - fn take_error(self, buffer: &mut Self::Buffer) -> crate::error::Error; - - fn is_word(&self) -> bool; - fn take_word(self, buffer: &Self::Buffer) -> Option; - fn is_keyword(&self) -> bool; - - fn is_reserved(&self, ctx: super::Context) -> bool; - fn into_atom(self, lexer: &mut Self::Lexer) -> Option; - fn follows_keyword_let(&self) -> bool; - - fn is_bin_op(&self) -> bool; - fn as_bin_op(&self) -> Option; - - fn is_assign_op(&self) -> bool; - fn as_assign_op(&self) -> Option; - - #[inline(always)] - fn is_less(&self) -> bool { - Self::LESS.eq(self) - } - #[inline(always)] - fn is_less_eq(&self) -> bool { - Self::LESS_EQ.eq(self) - } - #[inline(always)] - fn is_greater(&self) -> bool { - Self::GREATER.eq(self) - } - #[inline(always)] - fn is_colon(&self) -> bool { - Self::COLON.eq(self) - } - #[inline(always)] - fn is_comma(&self) -> bool { - Self::COMMA.eq(self) - } - #[inline(always)] - fn is_equal(&self) -> bool { - Self::EQUAL.eq(self) - } - #[inline(always)] - fn is_question(&self) -> bool { - Self::QUESTION.eq(self) - } - #[inline(always)] - fn is_null(&self) -> bool { - Self::NULL.eq(self) - } - #[inline(always)] - fn is_lshift(&self) -> bool { - Self::LSHIFT.eq(self) - } - #[inline(always)] - fn is_rshift(&self) -> bool { - Self::RSHIFT.eq(self) - } - #[inline(always)] - fn is_rshift_eq(&self) -> bool { - Self::RSHIFT_EQ.eq(self) - } - #[inline(always)] - fn is_greater_eq(&self) -> bool { - Self::GREATER_EQ.eq(self) - } - #[inline(always)] - fn is_true(&self) -> bool { - Self::TRUE.eq(self) - } - #[inline(always)] - fn is_false(&self) -> bool { - Self::FALSE.eq(self) - } - #[inline(always)] - fn is_enum(&self) -> bool { - Self::ENUM.eq(self) - } - #[inline(always)] - fn is_yield(&self) -> bool { - Self::YIELD.eq(self) - } - #[inline(always)] - fn is_let(&self) -> bool { - Self::LET.eq(self) - } - #[inline(always)] - fn is_var(&self) -> bool { - Self::VAR.eq(self) - } - #[inline(always)] - fn is_static(&self) -> bool { - Self::STATIC.eq(self) - } - #[inline(always)] - fn is_extends(&self) -> bool { - Self::EXTENDS.eq(self) - } - #[inline(always)] - fn is_implements(&self) -> bool { - Self::IMPLEMENTS.eq(self) - } - #[inline(always)] - fn is_interface(&self) -> bool { - Self::INTERFACE.eq(self) - } - #[inline(always)] - fn is_type(&self) -> bool { - Self::TYPE.eq(self) - } - #[inline(always)] - fn is_package(&self) -> bool { - Self::PACKAGE.eq(self) - } - #[inline(always)] - fn is_private(&self) -> bool { - Self::PRIVATE.eq(self) - } - #[inline(always)] - fn is_protected(&self) -> bool { - Self::PROTECTED.eq(self) - } - #[inline(always)] - fn is_public(&self) -> bool { - Self::PUBLIC.eq(self) - } - #[inline(always)] - fn is_readonly(&self) -> bool { - Self::READONLY.eq(self) - } - #[inline(always)] - fn is_await(&self) -> bool { - Self::AWAIT.eq(self) - } - #[inline(always)] - fn is_break(&self) -> bool { - Self::BREAK.eq(self) - } - #[inline(always)] - fn is_continue(&self) -> bool { - Self::CONTINUE.eq(self) - } - #[inline(always)] - fn is_arrow(&self) -> bool { - Self::ARROW.eq(self) - } - #[inline(always)] - fn is_this(&self) -> bool { - Self::THIS.eq(self) - } - #[inline(always)] - fn is_super(&self) -> bool { - Self::SUPER.eq(self) - } - #[inline(always)] - fn is_using(&self) -> bool { - Self::USING.eq(self) - } - #[inline(always)] - fn is_backquote(&self) -> bool { - Self::BACKQUOTE.eq(self) - } - #[inline(always)] - fn is_lparen(&self) -> bool { - Self::LPAREN.eq(self) - } - #[inline(always)] - fn is_rparen(&self) -> bool { - Self::RPAREN.eq(self) - } - #[inline(always)] - fn is_lbracket(&self) -> bool { - Self::LBRACKET.eq(self) - } - #[inline(always)] - fn is_rbracket(&self) -> bool { - Self::RBRACKET.eq(self) - } - #[inline(always)] - fn is_lbrace(&self) -> bool { - Self::LBRACE.eq(self) - } - #[inline(always)] - fn is_rbrace(&self) -> bool { - Self::RBRACE.eq(self) - } - #[inline(always)] - fn is_function(&self) -> bool { - Self::FUNCTION.eq(self) - } - #[inline(always)] - fn is_class(&self) -> bool { - Self::CLASS.eq(self) - } - #[inline(always)] - fn is_if(&self) -> bool { - Self::IF.eq(self) - } - #[inline(always)] - fn is_return(&self) -> bool { - Self::RETURN.eq(self) - } - #[inline(always)] - fn is_switch(&self) -> bool { - Self::SWITCH.eq(self) - } - #[inline(always)] - fn is_throw(&self) -> bool { - Self::THROW.eq(self) - } - #[inline(always)] - fn is_catch(&self) -> bool { - Self::CATCH.eq(self) - } - #[inline(always)] - fn is_finally(&self) -> bool { - Self::FINALLY.eq(self) - } - #[inline(always)] - fn is_try(&self) -> bool { - Self::TRY.eq(self) - } - #[inline(always)] - fn is_with(&self) -> bool { - Self::WITH.eq(self) - } - #[inline(always)] - fn is_while(&self) -> bool { - Self::WHILE.eq(self) - } - #[inline(always)] - fn is_new(&self) -> bool { - Self::NEW.eq(self) - } - #[inline(always)] - fn is_ident_ref(&self, ctx: Context) -> bool { - self.is_word() && !self.is_reserved(ctx) - } - #[inline(always)] - fn is_import(&self) -> bool { - Self::IMPORT.eq(self) - } - #[inline(always)] - fn is_export(&self) -> bool { - Self::EXPORT.eq(self) - } - #[inline(always)] - fn is_dot(&self) -> bool { - Self::DOT.eq(self) - } - #[inline(always)] - fn is_do(&self) -> bool { - Self::DO.eq(self) - } - #[inline(always)] - fn is_for(&self) -> bool { - Self::FOR.eq(self) - } - #[inline(always)] - fn is_from(&self) -> bool { - Self::FROM.eq(self) - } - #[inline(always)] - fn is_dotdotdot(&self) -> bool { - Self::DOTDOTDOT.eq(self) - } - #[inline(always)] - fn is_plus(&self) -> bool { - Self::PLUS.eq(self) - } - #[inline(always)] - fn is_minus(&self) -> bool { - Self::MINUS.eq(self) - } - #[inline(always)] - fn is_bang(&self) -> bool { - Self::BANG.eq(self) - } - #[inline(always)] - fn is_tilde(&self) -> bool { - Self::TILDE.eq(self) - } - #[inline(always)] - fn is_plus_plus(&self) -> bool { - Self::PLUS_PLUS.eq(self) - } - #[inline(always)] - fn is_minus_minus(&self) -> bool { - Self::MINUS_MINUS.eq(self) - } - #[inline(always)] - fn is_delete(&self) -> bool { - Self::DELETE.eq(self) - } - #[inline(always)] - fn is_typeof(&self) -> bool { - Self::TYPEOF.eq(self) - } - #[inline(always)] - fn is_of(&self) -> bool { - Self::OF.eq(self) - } - #[inline(always)] - fn is_void(&self) -> bool { - Self::VOID.eq(self) - } - #[inline(always)] - fn is_hash(&self) -> bool { - Self::HASH.eq(self) - } - #[inline(always)] - fn is_in(&self) -> bool { - Self::IN.eq(self) - } - #[inline(always)] - fn is_const(&self) -> bool { - Self::CONST.eq(self) - } - #[inline(always)] - fn is_star(&self) -> bool { - Self::MUL.eq(self) - } - #[inline(always)] - fn is_mod(&self) -> bool { - Self::MOD.eq(self) - } - #[inline(always)] - fn is_semi(&self) -> bool { - Self::SEMI.eq(self) - } - #[inline(always)] - fn is_slash(&self) -> bool { - Self::DIV.eq(self) - } - #[inline(always)] - fn is_slash_eq(&self) -> bool { - Self::DIV_EQ.eq(self) - } - #[inline(always)] - fn is_jsx_tag_start(&self) -> bool { - Self::JSX_TAG_START.eq(self) - } - #[inline(always)] - fn is_jsx_tag_end(&self) -> bool { - Self::JSX_TAG_END.eq(self) - } - #[inline(always)] - fn is_asserts(&self) -> bool { - Self::ASSERTS.eq(self) - } - #[inline(always)] - fn is_is(&self) -> bool { - Self::IS.eq(self) - } - #[inline(always)] - fn is_as(&self) -> bool { - Self::AS.eq(self) - } - #[inline(always)] - fn is_satisfies(&self) -> bool { - Self::SATISFIES.eq(self) - } - #[inline(always)] - fn is_instanceof(&self) -> bool { - Self::INSTANCEOF.eq(self) - } - #[inline(always)] - fn is_async(&self) -> bool { - Self::ASYNC.eq(self) - } - #[inline(always)] - fn is_case(&self) -> bool { - Self::CASE.eq(self) - } - #[inline(always)] - fn is_default(&self) -> bool { - Self::DEFAULT.eq(self) - } - #[inline(always)] - fn is_debugger(&self) -> bool { - Self::DEBUGGER.eq(self) - } - #[inline(always)] - fn is_bit_and(&self) -> bool { - Self::BIT_AND.eq(self) - } - #[inline(always)] - fn is_bit_or(&self) -> bool { - Self::BIT_OR.eq(self) - } - #[inline(always)] - fn is_exp(&self) -> bool { - Self::EXP.eq(self) - } - #[inline(always)] - fn is_eof(&self) -> bool { - Self::EOF.eq(self) - } - fn is_no_substitution_template_literal(&self) -> bool; - fn is_template_head(&self) -> bool; -} diff --git a/crates/swc_ecma_lexer/src/common/lexer/whitespace.rs b/crates/swc_ecma_lexer/src/common/lexer/whitespace.rs deleted file mode 100644 index 6037843a60cc..000000000000 --- a/crates/swc_ecma_lexer/src/common/lexer/whitespace.rs +++ /dev/null @@ -1,230 +0,0 @@ -/// Returns true if it's done -type ByteHandler = Option fn(&mut SkipWhitespace<'aa>) -> u32>; - -/// Lookup table for whitespace -static BYTE_HANDLERS: [ByteHandler; 256] = [ - // 0 1 2 3 4 5 6 7 8 9 A B C D E F // - ___, ___, ___, ___, ___, ___, ___, ___, ___, SPC, NLN, SPC, SPC, NLN, ___, ___, // 0 - ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, // 1 - SPC, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, // 2 - ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, // 3 - ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, // 4 - ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, // 5 - ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, // 6 - ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, // 7 - UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, // 8 - UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, // 9 - UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, // A - UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, // B - UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, // C - UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, // D - UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, // E - UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, UNI, // F -]; - -/// Stop -const ___: ByteHandler = None; - -/// Newline -const NLN: ByteHandler = Some(|skip| { - skip.newline = true; - - 1 -}); - -/// Space -const SPC: ByteHandler = Some(|_| 1); - -/// Unicode -const UNI: ByteHandler = Some(|skip| { - // Check byte patterns directly for more efficient Unicode character processing - let bytes = skip.input.as_bytes(); - let i = skip.offset as usize; - - // Check available bytes - let remaining_bytes = bytes.len() - i; - if remaining_bytes < 1 { - return 0; - } - - // Predict UTF-8 character length from the first byte - let first_byte = unsafe { *bytes.get_unchecked(i) }; - let char_len = if first_byte < 128 { - 1 - } else if first_byte < 224 { - if remaining_bytes < 2 { - return 0; - } - 2 - } else if first_byte < 240 { - if remaining_bytes < 3 { - return 0; - } - 3 - } else { - if remaining_bytes < 4 { - return 0; - } - 4 - }; - - // Fast path for common Unicode whitespace characters - // Check UTF-8 byte patterns directly - if char_len == 3 { - // LSEP (U+2028) - Line Separator: E2 80 A8 - if first_byte == 0xe2 - && unsafe { *bytes.get_unchecked(i + 1) } == 0x80 - && unsafe { *bytes.get_unchecked(i + 2) } == 0xa8 - { - skip.newline = true; - return 3; - } - - // PSEP (U+2029) - Paragraph Separator: E2 80 A9 - if first_byte == 0xe2 - && unsafe { *bytes.get_unchecked(i + 1) } == 0x80 - && unsafe { *bytes.get_unchecked(i + 2) } == 0xa9 - { - skip.newline = true; - return 3; - } - } - - // Process with general method if not handled by fast path - let s = unsafe { - // Safety: `skip.offset` is always valid - skip.input.get_unchecked(skip.offset as usize..) - }; - - let c = unsafe { - // Safety: byte handlers are only called when `skip.input` is not empty - s.chars().next().unwrap_unchecked() - }; - - match c { - // Byte Order Mark (BOM) - '\u{feff}' => {} - // Line break characters already handled above - '\u{2028}' | '\u{2029}' => { - skip.newline = true; - } - // Other whitespace characters - _ if c.is_whitespace() => {} - // Not a whitespace character - _ => return 0, - } - - c.len_utf8() as u32 -}); - -/// API is taked from oxc by Boshen (https://github.com/Boshen/oxc/pull/26) -pub(super) struct SkipWhitespace<'a> { - pub input: &'a str, - - /// Total offset - pub offset: u32, - - /// Found newline - pub newline: bool, -} - -impl SkipWhitespace<'_> { - #[inline(always)] - pub fn scan(&mut self) { - let bytes = self.input.as_bytes(); - let len = bytes.len(); - let mut pos = self.offset as usize; - debug_assert!(pos == 0); - debug_assert!(pos <= len); - - // Optimization: return immediately if input is empty - if pos == len { - return; - } - - loop { - // Optimization 1: Process consecutive spaces (most common case) at once - let mut byte = unsafe { *bytes.get_unchecked(pos) }; - - // Handle consecutive space characters (very common case) - if byte == b' ' { - pos += 1; - // Skip spaces repeatedly (process multiple spaces at once) - while pos < len && unsafe { *bytes.get_unchecked(pos) } == b' ' { - pos += 1; - } - - // Check if we've reached the end of input - if pos >= len { - break; - } - - // Get current byte again - byte = unsafe { *bytes.get_unchecked(pos) }; - } - - // Optimization 2: Handle other common whitespace characters - match byte { - b'\n' => { - pos += 1; - self.newline = true; - - if pos >= len { - break; - } - continue; - } - b'\r' => { - pos += 1; - - // Handle CR+LF sequence (Windows line break) - if pos < len && unsafe { *bytes.get_unchecked(pos) } == b'\n' { - pos += 1; - self.newline = true; - } else { - self.newline = true; // Treat standalone CR as line - // break too - } - - if pos >= len { - break; - } - continue; - } - // Case where handler is needed - _ => { - debug_assert!(byte != b' ' && byte != b'\n' && byte != b'\r'); - // Temporarily update offset - self.offset = pos as u32; - - // Use handler table - let handler = unsafe { BYTE_HANDLERS.get_unchecked(byte as usize) }; - - match handler { - Some(handler) => { - let delta = handler(self); - if delta == 0 { - // Non-whitespace character found - // offset is already updated - return; - } - pos = (self.offset + delta) as usize; - - if pos >= len { - break; - } - } - None => { - // Non-whitespace character found - // offset is already updated - return; - } - } - } - } - } - - // Update offset to final position - self.offset = pos as u32; - } -} diff --git a/crates/swc_ecma_lexer/src/common/mod.rs b/crates/swc_ecma_lexer/src/common/mod.rs deleted file mode 100644 index eb1da1e4aee6..000000000000 --- a/crates/swc_ecma_lexer/src/common/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -pub mod context; -pub mod input; -pub mod lexer; -pub mod parser; -pub mod syntax; diff --git a/crates/swc_ecma_lexer/src/common/parser/assign_target_or_spread.rs b/crates/swc_ecma_lexer/src/common/parser/assign_target_or_spread.rs deleted file mode 100644 index 19cefbd6b2ff..000000000000 --- a/crates/swc_ecma_lexer/src/common/parser/assign_target_or_spread.rs +++ /dev/null @@ -1,10 +0,0 @@ -use swc_common::ast_node; -use swc_ecma_ast::{ExprOrSpread, Pat}; - -#[ast_node] -pub enum AssignTargetOrSpread { - #[tag("ExprOrSpread")] - ExprOrSpread(ExprOrSpread), - #[tag("*")] - Pat(Pat), -} diff --git a/crates/swc_ecma_lexer/src/common/parser/buffer.rs b/crates/swc_ecma_lexer/src/common/parser/buffer.rs deleted file mode 100644 index 16b8f0f513bb..000000000000 --- a/crates/swc_ecma_lexer/src/common/parser/buffer.rs +++ /dev/null @@ -1,248 +0,0 @@ -use swc_atoms::{Atom, Wtf8Atom}; -use swc_common::{BytePos, Span}; -use swc_ecma_ast::EsVersion; - -use super::token_and_span::TokenAndSpan as TokenAndSpanTrait; -use crate::common::{ - context::Context, - input::Tokens, - lexer::{token::TokenFactory, LexResult}, - syntax::SyntaxFlags, -}; - -pub trait NextTokenAndSpan { - type Token; - fn token(&self) -> &Self::Token; - fn span(&self) -> Span; - fn had_line_break(&self) -> bool; -} - -pub trait Buffer<'a> { - type Token: std::fmt::Debug + PartialEq + Clone + TokenFactory<'a, Self::TokenAndSpan, Self::I>; - type Next: NextTokenAndSpan; - type TokenAndSpan: TokenAndSpanTrait; - type I: Tokens; - - fn new(lexer: Self::I) -> Self; - fn iter(&self) -> &Self::I; - fn iter_mut(&mut self) -> &mut Self::I; - - fn set_cur(&mut self, token: Self::TokenAndSpan); - fn next(&self) -> Option<&Self::Next>; - fn set_next(&mut self, token: Option); - fn next_mut(&mut self) -> &mut Option; - - fn cur(&self) -> &Self::Token; - fn get_cur(&self) -> &Self::TokenAndSpan; - - fn prev_span(&self) -> Span; - - fn peek<'b>(&'b mut self) -> Option<&'b Self::Token> - where - Self::TokenAndSpan: 'b; - - fn store(&mut self, token: Self::Token) { - debug_assert!(self.next().is_none()); - debug_assert!(!self.cur().is_eof()); - let span = self.prev_span(); - let token = Self::TokenAndSpan::new(token, span, false); - self.set_cur(token); - } - - fn dump_cur(&self) -> String; - - /// find next token. - fn bump(&mut self); - fn expect_word_token_and_bump(&mut self) -> Atom; - fn expect_number_token_and_bump(&mut self) -> (f64, Atom); - fn expect_string_token_and_bump(&mut self) -> (Wtf8Atom, Atom); - fn expect_bigint_token_and_bump(&mut self) -> (Box, Atom); - fn expect_regex_token_and_bump(&mut self) -> (Atom, Atom); - fn expect_template_token_and_bump(&mut self) -> (LexResult, Atom); - fn expect_error_token_and_bump(&mut self) -> crate::error::Error; - fn expect_jsx_name_token_and_bump(&mut self) -> Atom; - fn expect_jsx_text_token_and_bump(&mut self) -> (Atom, Atom); - fn expect_shebang_token_and_bump(&mut self) -> Atom; - - fn had_line_break_before_cur(&self) -> bool { - self.get_cur().had_line_break() - } - - /// This returns true on eof. - fn has_linebreak_between_cur_and_peeked(&mut self) -> bool { - let _ = self.peek(); - self.next().map(|item| item.had_line_break()).unwrap_or({ - // return true on eof. - true - }) - } - - fn cut_lshift(&mut self) { - debug_assert!( - self.is(&Self::Token::LSHIFT), - "parser should only call cut_lshift when encountering LShift token" - ); - let span = self.cur_span().with_lo(self.cur_span().lo + BytePos(1)); - let token = Self::TokenAndSpan::new(Self::Token::LESS, span, false); - self.set_cur(token); - } - - fn merge_lt_gt(&mut self) { - debug_assert!( - self.is(&Self::Token::LESS) || self.is(&Self::Token::GREATER), - "parser should only call merge_lt_gt when encountering Less token" - ); - if self.peek().is_none() { - return; - } - let span = self.cur_span(); - let next = self.next().unwrap(); - if span.hi != next.span().lo { - return; - } - let next = self.next_mut().take().unwrap(); - let cur = self.get_cur(); - let cur_token = cur.token(); - let token = if cur_token.is_greater() { - let next_token = next.token(); - if next_token.is_greater() { - // >> - Self::Token::RSHIFT - } else if next_token.is_equal() { - // >= - Self::Token::GREATER_EQ - } else if next_token.is_rshift() { - // >>> - Self::Token::ZERO_FILL_RSHIFT - } else if next_token.is_greater_eq() { - // >>= - Self::Token::RSHIFT_EQ - } else if next_token.is_rshift_eq() { - // >>>= - Self::Token::ZERO_FILL_RSHIFT_EQ - } else { - self.set_next(Some(next)); - return; - } - } else if cur_token.is_less() { - let next_token = next.token(); - if next_token.is_less() { - // << - Self::Token::LSHIFT - } else if next_token.is_equal() { - // <= - Self::Token::LESS_EQ - } else if next_token.is_less_eq() { - // <<= - Self::Token::LSHIFT_EQ - } else { - self.set_next(Some(next)); - return; - } - } else { - self.set_next(Some(next)); - return; - }; - let span = span.with_hi(next.span().hi); - let token = Self::TokenAndSpan::new(token, span, cur.had_line_break()); - self.set_cur(token); - } - - #[inline(always)] - fn is(&self, expected: &Self::Token) -> bool { - self.cur() == expected - } - - #[inline(always)] - fn eat(&mut self, expected: &Self::Token) -> bool { - let v = self.is(expected); - if v { - self.bump(); - } - v - } - - /// Returns start of current token. - #[inline] - fn cur_pos(&self) -> BytePos { - self.get_cur().span().lo - } - - #[inline] - fn cur_span(&self) -> Span { - self.get_cur().span() - } - - /// Returns last byte position of previous token. - #[inline] - fn last_pos(&self) -> BytePos { - self.prev_span().hi - } - - #[inline] - fn get_ctx(&self) -> Context { - self.iter().ctx() - } - - #[inline] - fn update_ctx(&mut self, f: impl FnOnce(&mut Context)) { - let ctx = self.iter_mut().ctx_mut(); - f(ctx) - } - - #[inline] - fn set_ctx(&mut self, ctx: Context) { - self.iter_mut().set_ctx(ctx); - } - - #[inline] - fn syntax(&self) -> SyntaxFlags { - self.iter().syntax() - } - - #[inline] - fn target(&self) -> EsVersion { - self.iter().target() - } - - #[inline] - fn set_expr_allowed(&mut self, allow: bool) { - self.iter_mut().set_expr_allowed(allow) - } - - #[inline] - fn set_next_regexp(&mut self, start: Option) { - self.iter_mut().set_next_regexp(start); - } - - #[inline] - fn token_context<'b>(&'b self) -> &'b crate::lexer::TokenContexts - where - Self::I: 'b, - { - self.iter().token_context() - } - - #[inline] - fn token_context_mut<'b>(&'b mut self) -> &'b mut crate::lexer::TokenContexts - where - Self::I: 'b, - { - self.iter_mut().token_context_mut() - } - - #[inline] - fn set_token_context(&mut self, c: crate::lexer::TokenContexts) { - self.iter_mut().set_token_context(c); - } - - #[inline] - fn end_pos(&self) -> BytePos { - self.iter().end_pos() - } - - #[inline] - fn token_flags(&self) -> crate::lexer::TokenFlags { - self.iter().token_flags() - } -} diff --git a/crates/swc_ecma_lexer/src/common/parser/class_and_fn.rs b/crates/swc_ecma_lexer/src/common/parser/class_and_fn.rs deleted file mode 100644 index 11dd4ba50944..000000000000 --- a/crates/swc_ecma_lexer/src/common/parser/class_and_fn.rs +++ /dev/null @@ -1,1690 +0,0 @@ -use swc_atoms::atom; -use swc_common::{BytePos, Span, Spanned}; -use swc_ecma_ast::*; - -use super::{ - buffer::Buffer, - expr::{parse_args, parse_assignment_expr}, - has_use_strict, - ident::{parse_binding_ident, parse_opt_binding_ident, parse_private_name}, - is_constructor, - output_type::OutputType, - pat::parse_formal_params, - stmt::parse_block, - typescript::{parse_ts_modifier, parse_ts_type_args, try_parse_ts_type_ann}, - PResult, Parser, -}; -use crate::{ - common::{ - context::Context, - lexer::token::TokenFactory, - parser::{ - expr::parse_subscripts, - ident::parse_ident, - is_invalid_class_name::IsInvalidClassName, - is_not_this, - is_simple_param_list::IsSimpleParameterList, - pat::{parse_constructor_params, parse_unique_formal_params}, - typescript::{ - parse_ts_heritage_clause, parse_ts_type_ann, parse_ts_type_or_type_predicate_ann, - parse_ts_type_params, try_parse_ts_index_signature, try_parse_ts_type_params, - }, - }, - }, - error::SyntaxError, - TokenContext, -}; - -struct MakeMethodArgs { - start: BytePos, - accessibility: Option, - is_abstract: bool, - static_token: Option, - decorators: Vec, - is_optional: bool, - is_override: bool, - key: Key, - kind: MethodKind, - is_async: bool, - is_generator: bool, -} - -/// If `required` is `true`, this never returns `None`. -pub fn parse_maybe_opt_binding_ident<'a>( - p: &mut impl Parser<'a>, - required: bool, - disallow_let: bool, -) -> PResult> { - if required { - parse_binding_ident(p, disallow_let).map(|v| v.id).map(Some) - } else { - parse_opt_binding_ident(p, disallow_let).map(|v| v.map(|v| v.id)) - } -} - -fn parse_maybe_decorator_args<'a, P: Parser<'a>>(p: &mut P, expr: Box) -> PResult> { - let type_args = if p.input().syntax().typescript() && p.input().is(&P::Token::LESS) { - let ret = parse_ts_type_args(p)?; - p.assert_and_bump(&P::Token::GREATER); - Some(ret) - } else { - None - }; - - if type_args.is_none() && !p.input().is(&P::Token::LPAREN) { - return Ok(expr); - } - - let args = parse_args(p, false)?; - Ok(CallExpr { - span: p.span(expr.span_lo()), - callee: Callee::Expr(expr), - args, - ..Default::default() - } - .into()) -} - -pub fn parse_decorators<'a, P: Parser<'a>>( - p: &mut P, - allow_export: bool, -) -> PResult> { - if !p.syntax().decorators() { - return Ok(Vec::new()); - } - trace_cur!(p, parse_decorators); - - let mut decorators = Vec::new(); - let start = p.cur_pos(); - - while p.input().is(&P::Token::AT) { - decorators.push(parse_decorator(p)?); - } - if decorators.is_empty() { - return Ok(decorators); - } - - if p.input().is(&P::Token::EXPORT) { - if !p.ctx().contains(Context::InClass) - && !p.ctx().contains(Context::InFunction) - && !allow_export - { - syntax_error!(p, p.input().cur_span(), SyntaxError::ExportNotAllowed); - } - - if !p.ctx().contains(Context::InClass) - && !p.ctx().contains(Context::InFunction) - && !p.syntax().decorators_before_export() - { - syntax_error!(p, p.span(start), SyntaxError::DecoratorOnExport); - } - } else if !p.input().is(&P::Token::CLASS) { - // syntax_error!(p, p.span(start), - // SyntaxError::InvalidLeadingDecorator) - } - - Ok(decorators) -} - -fn parse_decorator<'a, P: Parser<'a>>(p: &mut P) -> PResult { - let start = p.cur_pos(); - trace_cur!(p, parse_decorator); - - p.assert_and_bump(&P::Token::AT); - - let expr = if p.input_mut().eat(&P::Token::LPAREN) { - let expr = p.parse_expr()?; - expect!(p, &P::Token::RPAREN); - expr - } else { - let expr = parse_ident(p, false, false).map(Expr::from).map(Box::new)?; - - parse_subscripts(p, Callee::Expr(expr), false, true)? - }; - - let expr = parse_maybe_decorator_args(p, expr)?; - - Ok(Decorator { - span: p.span(start), - expr, - }) -} - -pub fn parse_access_modifier<'a>(p: &mut impl Parser<'a>) -> PResult> { - Ok( - parse_ts_modifier(p, &["public", "protected", "private", "in", "out"], false)?.and_then( - |s| match s { - "public" => Some(Accessibility::Public), - "protected" => Some(Accessibility::Protected), - "private" => Some(Accessibility::Private), - other => { - p.emit_err(p.input().prev_span(), SyntaxError::TS1274(other.into())); - None - } - }, - ), - ) -} - -pub fn parse_super_class<'a, P: Parser<'a>>( - p: &mut P, -) -> PResult<(Box, Option>)> { - let super_class = p.parse_lhs_expr()?; - match *super_class { - Expr::TsInstantiation(TsInstantiation { - expr, type_args, .. - }) => Ok((expr, Some(type_args))), - _ => { - // We still need to parse TS type arguments, - // because in some cases "super class" returned by `parse_lhs_expr` - // may not include `TsExprWithTypeArgs` - // but it's a super class with type params, for example, in JSX. - if p.syntax().typescript() && p.input().is(&P::Token::LESS) { - let ret = parse_ts_type_args(p)?; - p.assert_and_bump(&P::Token::GREATER); - Ok((super_class, Some(ret))) - } else { - Ok((super_class, None)) - } - } - } -} - -pub fn is_class_method<'a, P: Parser<'a>>(p: &mut P) -> bool { - let cur = p.input().cur(); - cur == &P::Token::LPAREN - || (p.input().syntax().typescript() && (cur.is_less() || cur.is_jsx_tag_start())) -} - -pub fn is_class_property<'a, P: Parser<'a>>(p: &mut P, asi: bool) -> bool { - let cur = p.input().cur(); - (p.input().syntax().typescript() && (cur.is_bang() || cur.is_colon())) - || (cur.is_equal() || cur.is_rbrace()) - || if asi { - p.is_general_semi() - } else { - p.input().is(&P::Token::SEMI) - } -} - -pub fn parse_class_prop_name<'a, P: Parser<'a>>(p: &mut P) -> PResult { - if p.input().is(&P::Token::HASH) { - let name = parse_private_name(p)?; - if name.name == "constructor" { - p.emit_err(name.span, SyntaxError::PrivateConstructor); - } - Ok(Key::Private(name)) - } else { - p.parse_prop_name().map(Key::Public) - } -} - -/// `parse_args` closure should not eat '(' or ')'. -pub fn parse_fn_args_body<'a, P: Parser<'a>, F>( - p: &mut P, - decorators: Vec, - start: BytePos, - parse_args: F, - is_async: bool, - is_generator: bool, -) -> PResult> -where - F: FnOnce(&mut P) -> PResult>, -{ - trace_cur!(p, parse_fn_args_body); - let f = |p: &mut P| { - let type_params = if p.syntax().typescript() { - p.in_type(|p| { - trace_cur!(p, parse_fn_args_body__type_params); - - Ok(if p.input().is(&P::Token::LESS) { - Some(parse_ts_type_params(p, false, true)?) - } else if p.input().is(&P::Token::JSX_TAG_START) { - debug_assert_eq!( - p.input().token_context().current(), - Some(TokenContext::JSXOpeningTag) - ); - p.input_mut().token_context_mut().pop(); - debug_assert_eq!( - p.input().token_context().current(), - Some(TokenContext::JSXExpr) - ); - p.input_mut().token_context_mut().pop(); - - Some(parse_ts_type_params(p, false, true)?) - } else { - None - }) - })? - } else { - None - }; - - expect!(p, &P::Token::LPAREN); - - let parse_args_with_generator_ctx = |p: &mut P| { - if is_generator { - p.do_inside_of_context(Context::InGenerator, parse_args) - } else { - p.do_outside_of_context(Context::InGenerator, parse_args) - } - }; - - let params = p.do_inside_of_context(Context::InParameters, |p| { - p.do_outside_of_context(Context::InFunction, |p| { - if is_async { - p.do_inside_of_context(Context::InAsync, parse_args_with_generator_ctx) - } else { - p.do_outside_of_context(Context::InAsync, parse_args_with_generator_ctx) - } - }) - })?; - - expect!(p, &P::Token::RPAREN); - - // typescript extension - let return_type = if p.syntax().typescript() && p.input().is(&P::Token::COLON) { - parse_ts_type_or_type_predicate_ann(p, &P::Token::COLON).map(Some)? - } else { - None - }; - - let body: Option<_> = parse_fn_block_body( - p, - is_async, - is_generator, - false, - params.is_simple_parameter_list(), - )?; - - if p.syntax().typescript() && body.is_none() { - // Declare functions cannot have assignment pattern in parameters - for param in ¶ms { - // TODO: Search deeply for assignment pattern using a Visitor - - let span = match ¶m.pat { - Pat::Assign(ref p) => Some(p.span()), - _ => None, - }; - - if let Some(span) = span { - p.emit_err(span, SyntaxError::TS2371) - } - } - } - - Ok(Box::new(Function { - span: p.span(start), - decorators, - type_params, - params, - body, - is_async, - is_generator, - return_type, - ctxt: Default::default(), - })) - }; - - let f_with_generator_ctx = |p: &mut P| { - if is_generator { - p.do_inside_of_context(Context::InGenerator, f) - } else { - p.do_outside_of_context(Context::InGenerator, f) - } - }; - - if is_async { - p.do_inside_of_context(Context::InAsync, f_with_generator_ctx) - } else { - p.do_outside_of_context(Context::InAsync, f_with_generator_ctx) - } -} - -pub fn parse_async_fn_expr<'a, P: Parser<'a>>(p: &mut P) -> PResult> { - let start = p.cur_pos(); - expect!(p, &P::Token::ASYNC); - parse_fn(p, None, Some(start), Vec::new()) -} - -/// Parse function expression -pub fn parse_fn_expr<'a, P: Parser<'a>>(p: &mut P) -> PResult> { - parse_fn(p, None, None, Vec::new()) -} - -pub fn parse_async_fn_decl<'a, P: Parser<'a>>( - p: &mut P, - decorators: Vec, -) -> PResult { - let start = p.cur_pos(); - expect!(p, &P::Token::ASYNC); - parse_fn(p, None, Some(start), decorators) -} - -pub fn parse_fn_decl<'a, P: Parser<'a>>(p: &mut P, decorators: Vec) -> PResult { - parse_fn(p, None, None, decorators) -} - -pub fn parse_default_async_fn<'a, P: Parser<'a>>( - p: &mut P, - start: BytePos, - decorators: Vec, -) -> PResult { - let start_of_async = p.cur_pos(); - expect!(p, &P::Token::ASYNC); - parse_fn(p, Some(start), Some(start_of_async), decorators) -} - -pub fn parse_default_fn<'a, P: Parser<'a>>( - p: &mut P, - start: BytePos, - decorators: Vec, -) -> PResult { - parse_fn(p, Some(start), None, decorators) -} - -fn parse_fn_inner<'a, P: Parser<'a>>( - p: &mut P, - _start_of_output_type: Option, - start_of_async: Option, - decorators: Vec, - is_fn_expr: bool, - is_ident_required: bool, -) -> PResult<(Option, Box)> { - let start = start_of_async.unwrap_or_else(|| p.cur_pos()); - p.assert_and_bump(&P::Token::FUNCTION); - let is_async = start_of_async.is_some(); - - let is_generator = p.input_mut().eat(&P::Token::MUL); - - let ident = if is_fn_expr { - let f_with_generator_context = |p: &mut P| { - if is_generator { - p.do_inside_of_context(Context::InGenerator, |p| { - parse_maybe_opt_binding_ident(p, is_ident_required, false) - }) - } else { - p.do_outside_of_context(Context::InGenerator, |p| { - parse_maybe_opt_binding_ident(p, is_ident_required, false) - }) - } - }; - - p.do_outside_of_context( - Context::AllowDirectSuper.union(Context::InClassField), - |p| { - if is_async { - p.do_inside_of_context(Context::InAsync, f_with_generator_context) - } else { - p.do_outside_of_context(Context::InAsync, f_with_generator_context) - } - }, - )? - } else { - // function declaration does not change context for `BindingIdentifier`. - p.do_outside_of_context( - Context::AllowDirectSuper.union(Context::InClassField), - |p| parse_maybe_opt_binding_ident(p, is_ident_required, false), - )? - }; - - p.do_outside_of_context( - Context::AllowDirectSuper - .union(Context::InClassField) - .union(Context::WillExpectColonForCond), - |p| { - let f = parse_fn_args_body( - p, - decorators, - start, - parse_formal_params, - is_async, - is_generator, - )?; - if is_fn_expr && f.body.is_none() { - unexpected!(p, "{"); - } - Ok((ident, f)) - }, - ) -} - -fn parse_fn<'a, P: Parser<'a>, T>( - p: &mut P, - start_of_output_type: Option, - start_of_async: Option, - decorators: Vec, -) -> PResult -where - T: OutputType, -{ - let start = start_of_async.unwrap_or_else(|| p.cur_pos()); - let (ident, f) = parse_fn_inner( - p, - start_of_output_type, - start_of_async, - decorators, - T::is_fn_expr(), - T::IS_IDENT_REQUIRED, - )?; - - match T::finish_fn(p.span(start_of_output_type.unwrap_or(start)), ident, f) { - Ok(v) => Ok(v), - Err(kind) => syntax_error!(p, kind), - } -} - -pub fn parse_class_decl<'a, P: Parser<'a>>( - p: &mut P, - start: BytePos, - class_start: BytePos, - decorators: Vec, - is_abstract: bool, -) -> PResult { - parse_class(p, start, class_start, decorators, is_abstract) -} - -pub fn parse_class_expr<'a, P: Parser<'a>>( - p: &mut P, - start: BytePos, - decorators: Vec, -) -> PResult> { - parse_class(p, start, start, decorators, false) -} - -pub fn parse_default_class<'a, P: Parser<'a>>( - p: &mut P, - start: BytePos, - class_start: BytePos, - decorators: Vec, - is_abstract: bool, -) -> PResult { - parse_class(p, start, class_start, decorators, is_abstract) -} - -fn make_method<'a, P: Parser<'a>, F>( - p: &mut P, - parse_args: F, - MakeMethodArgs { - start, - accessibility, - is_abstract, - static_token, - decorators, - is_optional, - is_override, - key, - kind, - is_async, - is_generator, - }: MakeMethodArgs, -) -> PResult -where - F: FnOnce(&mut P) -> PResult>, -{ - trace_cur!(p, make_method); - - let is_static = static_token.is_some(); - let function = p.do_inside_of_context(Context::AllowDirectSuper, |p| { - p.do_outside_of_context(Context::InClassField, |p| { - parse_fn_args_body(p, decorators, start, parse_args, is_async, is_generator) - }) - })?; - - match kind { - MethodKind::Getter | MethodKind::Setter - if p.input().syntax().typescript() && p.input().target() == EsVersion::Es3 => - { - p.emit_err(key.span(), SyntaxError::TS1056); - } - _ => {} - } - - match key { - Key::Private(key) => { - let span = p.span(start); - if accessibility.is_some() { - p.emit_err(span.with_hi(key.span_hi()), SyntaxError::TS18010); - } - - Ok(PrivateMethod { - span, - - accessibility, - is_abstract, - is_optional, - is_override, - - is_static, - key, - function, - kind, - } - .into()) - } - Key::Public(key) => { - let span = p.span(start); - if is_abstract && function.body.is_some() { - p.emit_err(span, SyntaxError::TS1245) - } - Ok(ClassMethod { - span, - - accessibility, - is_abstract, - is_optional, - is_override, - - is_static, - key, - function, - kind, - } - .into()) - } - #[cfg(swc_ast_unknown)] - _ => unreachable!(), - } -} - -pub fn parse_fn_block_or_expr_body<'a, P: Parser<'a>>( - p: &mut P, - is_async: bool, - is_generator: bool, - is_arrow_function: bool, - is_simple_parameter_list: bool, -) -> PResult> { - parse_fn_body( - p, - is_async, - is_generator, - is_arrow_function, - is_simple_parameter_list, - |p, is_simple_parameter_list| { - if p.input().is(&P::Token::LBRACE) { - parse_block(p, false) - .map(|block_stmt| { - if !is_simple_parameter_list { - if let Some(span) = has_use_strict(&block_stmt) { - p.emit_err(span, SyntaxError::IllegalLanguageModeDirective); - } - } - BlockStmtOrExpr::BlockStmt(block_stmt) - }) - .map(Box::new) - } else { - parse_assignment_expr(p) - .map(BlockStmtOrExpr::Expr) - .map(Box::new) - } - }, - ) -} - -fn parse_fn_body<'a, P: Parser<'a>, T>( - p: &mut P, - is_async: bool, - is_generator: bool, - is_arrow_function: bool, - is_simple_parameter_list: bool, - f: impl FnOnce(&mut P, bool) -> PResult, -) -> PResult { - if p.ctx().contains(Context::InDeclare) - && p.syntax().typescript() - && p.input().is(&P::Token::LBRACE) - { - // p.emit_err( - // p.ctx().span_of_fn_name.expect("we are not in function"), - // SyntaxError::TS1183, - // ); - p.emit_err(p.input().cur_span(), SyntaxError::TS1183); - } - - let f_with_generator_context = |p: &mut P| { - let f_with_inside_non_arrow_fn_scope = |p: &mut P| { - let f_with_new_state = |p: &mut P| { - let mut p = p.with_state(crate::common::parser::state::State::default()); - f(&mut p, is_simple_parameter_list) - }; - - if is_arrow_function && !p.ctx().contains(Context::InsideNonArrowFunctionScope) { - p.do_outside_of_context(Context::InsideNonArrowFunctionScope, f_with_new_state) - } else { - p.do_inside_of_context(Context::InsideNonArrowFunctionScope, f_with_new_state) - } - }; - - if is_generator { - p.do_inside_of_context(Context::InGenerator, f_with_inside_non_arrow_fn_scope) - } else { - p.do_outside_of_context(Context::InGenerator, f_with_inside_non_arrow_fn_scope) - } - }; - - p.do_inside_of_context(Context::InFunction, |p| { - p.do_outside_of_context( - Context::InStaticBlock - .union(Context::IsBreakAllowed) - .union(Context::IsContinueAllowed) - .union(Context::TopLevel), - |p| { - if is_async { - p.do_inside_of_context(Context::InAsync, f_with_generator_context) - } else { - p.do_outside_of_context(Context::InAsync, f_with_generator_context) - } - }, - ) - }) -} - -pub(super) fn parse_fn_block_body<'a, P: Parser<'a>>( - p: &mut P, - is_async: bool, - is_generator: bool, - is_arrow_function: bool, - is_simple_parameter_list: bool, -) -> PResult> { - parse_fn_body( - p, - is_async, - is_generator, - is_arrow_function, - is_simple_parameter_list, - |p, is_simple_parameter_list| { - // allow omitting body and allow placing `{` on next line - if p.input().syntax().typescript() - && !p.input().is(&P::Token::LBRACE) - && p.eat_general_semi() - { - return Ok(None); - } - p.allow_in_expr(|p| parse_block(p, true)).map(|block_stmt| { - if !is_simple_parameter_list { - if let Some(span) = has_use_strict(&block_stmt) { - p.emit_err(span, SyntaxError::IllegalLanguageModeDirective); - } - } - Some(block_stmt) - }) - }, - ) -} - -fn make_property<'a, P: Parser<'a>>( - p: &mut P, - start: BytePos, - decorators: Vec, - accessibility: Option, - key: Key, - is_static: bool, - accessor_token: Option, - is_optional: bool, - readonly: bool, - declare: bool, - is_abstract: bool, - is_override: bool, -) -> PResult { - if is_constructor(&key) { - syntax_error!(p, key.span(), SyntaxError::PropertyNamedConstructor); - } - if key.is_private() { - if declare { - p.emit_err( - key.span(), - SyntaxError::PrivateNameModifier(atom!("declare")), - ) - } - if is_abstract { - p.emit_err( - key.span(), - SyntaxError::PrivateNameModifier(atom!("abstract")), - ) - } - } - let definite = - p.input().syntax().typescript() && !is_optional && p.input_mut().eat(&P::Token::BANG); - - let type_ann = try_parse_ts_type_ann(p)?; - - p.do_inside_of_context(Context::IncludeInExpr.union(Context::InClassField), |p| { - let value = if p.input().is(&P::Token::EQUAL) { - p.assert_and_bump(&P::Token::EQUAL); - Some(parse_assignment_expr(p)?) - } else { - None - }; - - if !p.eat_general_semi() { - p.emit_err(p.input().cur_span(), SyntaxError::TS1005); - } - - if accessor_token.is_some() { - return Ok(ClassMember::AutoAccessor(AutoAccessor { - span: p.span(start), - key, - value, - type_ann, - is_static, - decorators, - accessibility, - is_abstract, - is_override, - definite, - })); - } - - Ok(match key { - Key::Private(key) => { - let span = p.span(start); - if accessibility.is_some() { - p.emit_err(span.with_hi(key.span_hi()), SyntaxError::TS18010); - } - - PrivateProp { - span: p.span(start), - key, - value, - is_static, - decorators, - accessibility, - is_optional, - is_override, - readonly, - type_ann, - definite, - ctxt: Default::default(), - } - .into() - } - Key::Public(key) => { - let span = p.span(start); - if is_abstract && value.is_some() { - p.emit_err(span, SyntaxError::TS1267) - } - ClassProp { - span, - key, - value, - is_static, - decorators, - accessibility, - is_abstract, - is_optional, - is_override, - readonly, - declare, - definite, - type_ann, - } - .into() - } - #[cfg(swc_ast_unknown)] - _ => unreachable!(), - }) - }) -} - -fn parse_static_block<'a, P: Parser<'a>>(p: &mut P, start: BytePos) -> PResult { - let body = p.do_inside_of_context( - Context::InStaticBlock - .union(Context::InClassField) - .union(Context::AllowUsingDecl), - |p| parse_block(p, false), - )?; - - let span = p.span(start); - Ok(StaticBlock { span, body }.into()) -} - -fn parse_class_member_with_is_static<'a, P: Parser<'a>>( - p: &mut P, - start: BytePos, - declare_token: Option, - accessibility: Option, - static_token: Option, - accessor_token: Option, - decorators: Vec, -) -> PResult { - let mut is_static = static_token.is_some(); - - let mut is_abstract = false; - let mut is_override = false; - let mut readonly = None; - let mut modifier_span = None; - let declare = declare_token.is_some(); - while let Some(modifier) = if p.input().syntax().typescript() { - parse_ts_modifier(p, &["abstract", "readonly", "override", "static"], true)? - } else { - None - } { - modifier_span = Some(p.input().prev_span()); - match modifier { - "abstract" => { - if is_abstract { - p.emit_err( - p.input().prev_span(), - SyntaxError::TS1030(atom!("abstract")), - ); - } else if is_override { - p.emit_err( - p.input().prev_span(), - SyntaxError::TS1029(atom!("abstract"), atom!("override")), - ); - } - is_abstract = true; - } - "override" => { - if is_override { - p.emit_err( - p.input().prev_span(), - SyntaxError::TS1030(atom!("override")), - ); - } else if readonly.is_some() { - p.emit_err( - p.input().prev_span(), - SyntaxError::TS1029(atom!("override"), atom!("readonly")), - ); - } else if declare { - p.emit_err( - p.input().prev_span(), - SyntaxError::TS1243(atom!("override"), atom!("declare")), - ); - } else if !p.ctx().contains(Context::HasSuperClass) { - p.emit_err(p.input().prev_span(), SyntaxError::TS4112); - } - is_override = true; - } - "readonly" => { - let readonly_span = p.input().prev_span(); - if readonly.is_some() { - p.emit_err(readonly_span, SyntaxError::TS1030(atom!("readonly"))); - } else { - readonly = Some(readonly_span); - } - } - "static" => { - if is_override { - p.emit_err( - p.input().prev_span(), - SyntaxError::TS1029(atom!("static"), atom!("override")), - ); - } - - is_static = true; - } - _ => {} - } - } - - let accessor_token = accessor_token.or_else(|| { - if p.syntax().auto_accessors() && readonly.is_none() { - let start = p.cur_pos(); - if !peek!(p).is_some_and(|cur| cur.is_lparen()) - && p.input_mut().eat(&P::Token::ACCESSOR) - { - Some(p.span(start)) - } else { - None - } - } else { - None - } - }); - - if is_static && p.input().is(&P::Token::LBRACE) { - if let Some(span) = declare_token { - p.emit_err(span, SyntaxError::TS1184); - } - if accessibility.is_some() { - p.emit_err(p.input().cur_span(), SyntaxError::TS1184); - } - return parse_static_block(p, start); - } - if p.input().is(&P::Token::STATIC) && peek!(p).is_some_and(|cur| cur.is_lbrace()) { - // For "readonly", "abstract" and "override" - if let Some(span) = modifier_span { - p.emit_err(span, SyntaxError::TS1184); - } - if let Some(span) = static_token { - p.emit_err(span, SyntaxError::TS1184); - } - p.bump(); // consume "static" - return parse_static_block(p, start); - } - - if p.input().syntax().typescript() && !is_abstract && !is_override && accessibility.is_none() { - let idx = try_parse_ts_index_signature(p, start, readonly.is_some(), is_static)?; - if let Some(idx) = idx { - return Ok(idx.into()); - } - } - - if p.input_mut().eat(&P::Token::MUL) { - // generator method - let key = parse_class_prop_name(p)?; - if readonly.is_some() { - p.emit_err(p.span(start), SyntaxError::ReadOnlyMethod); - } - if is_constructor(&key) { - p.emit_err(p.span(start), SyntaxError::GeneratorConstructor); - } - - return make_method( - p, - parse_unique_formal_params, - MakeMethodArgs { - start, - decorators, - is_async: false, - is_generator: true, - accessibility, - is_abstract, - is_override, - is_optional: false, - static_token, - key, - kind: MethodKind::Method, - }, - ); - } - - trace_cur!(p, parse_class_member_with_is_static__normal_class_member); - let key = if readonly.is_some() && (p.input().cur().is_bang() || p.input().cur().is_colon()) { - Key::Public(PropName::Ident(IdentName::new( - atom!("readonly"), - readonly.unwrap(), - ))) - } else { - parse_class_prop_name(p)? - }; - let is_optional = p.input().syntax().typescript() && p.input_mut().eat(&P::Token::QUESTION); - - if is_class_method(p) { - // handle a(){} / get(){} / set(){} / async(){} - - trace_cur!(p, parse_class_member_with_is_static__normal_class_method); - - if let Some(token) = declare_token { - p.emit_err(token, SyntaxError::TS1031) - } - - if readonly.is_some() { - syntax_error!(p, p.span(start), SyntaxError::ReadOnlyMethod); - } - let is_constructor = is_constructor(&key); - - if is_constructor { - if p.syntax().typescript() && is_override { - p.emit_err(p.span(start), SyntaxError::TS1089(atom!("override"))); - } - - if p.syntax().typescript() && p.input().is(&P::Token::LESS) { - let start = p.cur_pos(); - if peek!(p).is_some_and(|cur| cur.is_less()) { - p.assert_and_bump(&P::Token::LESS); - let start2 = p.cur_pos(); - p.assert_and_bump(&P::Token::GREATER); - - p.emit_err(p.span(start), SyntaxError::TS1098); - p.emit_err(p.span(start2), SyntaxError::TS1092); - } else { - let type_params = try_parse_ts_type_params(p, false, true)?; - - if let Some(type_params) = type_params { - for param in type_params.params { - p.emit_err(param.span(), SyntaxError::TS1092); - } - } - } - } - - expect!(p, &P::Token::LPAREN); - let params = parse_constructor_params(p)?; - expect!(p, &P::Token::RPAREN); - - if p.syntax().typescript() && p.input().is(&P::Token::COLON) { - let start = p.cur_pos(); - let type_ann = parse_ts_type_ann(p, true, start)?; - - // Flow allows return type annotations on constructors. - if !p.syntax().flow() { - p.emit_err(type_ann.type_ann.span(), SyntaxError::TS1093); - } - } - - let body: Option<_> = - parse_fn_block_body(p, false, false, false, params.is_simple_parameter_list())?; - - if body.is_none() { - for param in params.iter() { - if param.is_ts_param_prop() { - p.emit_err(param.span(), SyntaxError::TS2369) - } - } - } - - if p.syntax().typescript() && body.is_none() { - // Declare constructors cannot have assignment pattern in parameters - for param in ¶ms { - // TODO: Search deeply for assignment pattern using a Visitor - - let span = match *param { - ParamOrTsParamProp::Param(ref param) => match param.pat { - Pat::Assign(ref p) => Some(p.span()), - _ => None, - }, - ParamOrTsParamProp::TsParamProp(TsParamProp { - param: TsParamPropParam::Assign(ref p), - .. - }) => Some(p.span()), - _ => None, - }; - - if let Some(span) = span { - p.emit_err(span, SyntaxError::TS2371) - } - } - } - - if let Some(static_token) = static_token { - p.emit_err(static_token, SyntaxError::TS1089(atom!("static"))) - } - - if let Some(span) = modifier_span { - if is_abstract { - p.emit_err(span, SyntaxError::TS1242); - } - } - - return Ok(ClassMember::Constructor(Constructor { - span: p.span(start), - accessibility, - key: match key { - Key::Public(key) => key, - _ => unreachable!("is_constructor() returns false for PrivateName"), - }, - is_optional, - params, - body, - ..Default::default() - })); - } else { - return make_method( - p, - parse_formal_params, - MakeMethodArgs { - start, - is_optional, - accessibility, - decorators, - is_abstract, - is_override, - static_token, - kind: MethodKind::Method, - key, - is_async: false, - is_generator: false, - }, - ); - } - } - - let is_next_line_generator = - p.input_mut().had_line_break_before_cur() && p.input().is(&P::Token::MUL); - let getter_or_setter_ident = match key { - // `get\n*` is an uninitialized property named 'get' followed by a generator. - Key::Public(PropName::Ident(ref i)) - if (i.sym == "get" || i.sym == "set") - && !is_class_property(p, /* asi */ false) - && !is_next_line_generator => - { - Some(i) - } - _ => None, - }; - - if getter_or_setter_ident.is_none() && is_class_property(p, /* asi */ true) { - return make_property( - p, - start, - decorators, - accessibility, - key, - is_static, - accessor_token, - is_optional, - readonly.is_some(), - declare, - is_abstract, - is_override, - ); - } - - if match key { - Key::Public(PropName::Ident(ref i)) => i.sym == "async", - _ => false, - } && !p.input_mut().had_line_break_before_cur() - { - // handle async foo(){} - - if p.input().syntax().typescript() && parse_ts_modifier(p, &["override"], false)?.is_some() - { - is_override = true; - p.emit_err( - p.input().prev_span(), - SyntaxError::TS1029(atom!("override"), atom!("async")), - ); - } - - let is_generator = p.input_mut().eat(&P::Token::MUL); - let key = parse_class_prop_name(p)?; - if is_constructor(&key) { - syntax_error!(p, key.span(), SyntaxError::AsyncConstructor) - } - if readonly.is_some() { - syntax_error!(p, p.span(start), SyntaxError::ReadOnlyMethod); - } - - // handle async foo(){} - let is_optional = is_optional - || p.input().syntax().typescript() && p.input_mut().eat(&P::Token::QUESTION); - return make_method( - p, - parse_unique_formal_params, - MakeMethodArgs { - start, - static_token, - key, - is_abstract, - accessibility, - is_optional, - is_override, - decorators, - kind: MethodKind::Method, - is_async: true, - is_generator, - }, - ); - } - - if let Some(i) = getter_or_setter_ident { - let key_span = key.span(); - - // handle get foo(){} / set foo(v){} - let key = parse_class_prop_name(p)?; - - if readonly.is_some() { - p.emit_err(key_span, SyntaxError::GetterSetterCannotBeReadonly); - } - - if is_constructor(&key) { - p.emit_err(key_span, SyntaxError::ConstructorAccessor); - } - - return match &*i.sym { - "get" => make_method( - p, - |p| { - let params = parse_formal_params(p)?; - - if params.iter().any(is_not_this) { - p.emit_err(key_span, SyntaxError::GetterParam); - } - - Ok(params) - }, - MakeMethodArgs { - decorators, - start, - is_abstract, - is_async: false, - is_generator: false, - is_optional, - is_override, - accessibility, - static_token, - key, - kind: MethodKind::Getter, - }, - ), - "set" => make_method( - p, - |p| { - let params = parse_formal_params(p)?; - - if params.iter().filter(|p| is_not_this(p)).count() != 1 { - p.emit_err(key_span, SyntaxError::SetterParam); - } - - if !params.is_empty() { - if let Pat::Rest(..) = params[0].pat { - p.emit_err(params[0].pat.span(), SyntaxError::RestPatInSetter); - } - } - - Ok(params) - }, - MakeMethodArgs { - decorators, - start, - is_optional, - is_abstract, - is_override, - is_async: false, - is_generator: false, - accessibility, - static_token, - key, - kind: MethodKind::Setter, - }, - ), - _ => unreachable!(), - }; - } - - unexpected!(p, "* for generator, private key, identifier or async") -} - -fn parse_class_member<'a, P: Parser<'a>>(p: &mut P) -> PResult { - trace_cur!(p, parse_class_member); - - let start = p.cur_pos(); - let decorators = parse_decorators(p, false)?; - let declare = p.syntax().typescript() && p.input_mut().eat(&P::Token::DECLARE); - let accessibility = if p.input().syntax().typescript() { - parse_access_modifier(p)? - } else { - None - }; - // Allow `private declare`. - let declare = declare || p.syntax().typescript() && p.input_mut().eat(&P::Token::DECLARE); - - let declare_token = if declare { - // Handle declare(){} - if is_class_method(p) { - let key = Key::Public(PropName::Ident(IdentName::new( - atom!("declare"), - p.span(start), - ))); - let is_optional = - p.input().syntax().typescript() && p.input_mut().eat(&P::Token::QUESTION); - return make_method( - p, - parse_unique_formal_params, - MakeMethodArgs { - start, - accessibility, - decorators, - is_abstract: false, - is_optional, - is_override: false, - is_async: false, - is_generator: false, - static_token: None, - key, - kind: MethodKind::Method, - }, - ); - } else if is_class_property(p, /* asi */ true) - || (p.syntax().typescript() && p.input().is(&P::Token::QUESTION)) - { - // Property named `declare` - - let key = Key::Public(PropName::Ident(IdentName::new( - atom!("declare"), - p.span(start), - ))); - let is_optional = - p.input().syntax().typescript() && p.input_mut().eat(&P::Token::QUESTION); - return make_property( - p, - start, - decorators, - accessibility, - key, - false, - None, - is_optional, - false, - false, - false, - false, - ); - } else { - Some(p.span(start)) - } - } else { - None - }; - - let static_token = { - let start = p.cur_pos(); - if p.input_mut().eat(&P::Token::STATIC) { - Some(p.span(start)) - } else { - None - } - }; - - let accessor_token = if p.syntax().auto_accessors() { - let start = p.cur_pos(); - if p.input_mut().eat(&P::Token::ACCESSOR) { - Some(p.span(start)) - } else { - None - } - } else { - None - }; - - if let Some(accessor_token) = accessor_token { - // Handle accessor(){} - if is_class_method(p) { - let key = Key::Public(PropName::Ident(IdentName::new( - atom!("accessor"), - accessor_token, - ))); - let is_optional = - p.input().syntax().typescript() && p.input_mut().eat(&P::Token::QUESTION); - return make_method( - p, - parse_unique_formal_params, - MakeMethodArgs { - start, - accessibility, - decorators, - is_abstract: false, - is_optional, - is_override: false, - is_async: false, - is_generator: false, - static_token, - key, - kind: MethodKind::Method, - }, - ); - } else if is_class_property(p, /* asi */ true) - || (p.syntax().typescript() && p.input().is(&P::Token::QUESTION)) - { - // Property named `accessor` - - let key = Key::Public(PropName::Ident(IdentName::new( - atom!("accessor"), - accessor_token, - ))); - let is_optional = - p.input().syntax().typescript() && p.input_mut().eat(&P::Token::QUESTION); - let is_static = static_token.is_some(); - return make_property( - p, - start, - decorators, - accessibility, - key, - is_static, - None, - is_optional, - false, - declare, - false, - false, - ); - } - } - - if let Some(static_token) = static_token { - // Handle static(){} - if is_class_method(p) { - let key = Key::Public(PropName::Ident(IdentName::new( - atom!("static"), - static_token, - ))); - let is_optional = - p.input().syntax().typescript() && p.input_mut().eat(&P::Token::QUESTION); - return make_method( - p, - parse_unique_formal_params, - MakeMethodArgs { - start, - accessibility, - decorators, - is_abstract: false, - is_optional, - is_override: false, - is_async: false, - is_generator: false, - static_token: None, - key, - kind: MethodKind::Method, - }, - ); - } else if is_class_property(p, /* asi */ false) - || (p.syntax().typescript() && p.input().is(&P::Token::QUESTION)) - { - // Property named `static` - - // Avoid to parse - // static - // {} - let is_parsing_static_blocks = p.input().is(&P::Token::LBRACE); - if !is_parsing_static_blocks { - let key = Key::Public(PropName::Ident(IdentName::new( - atom!("static"), - static_token, - ))); - let is_optional = - p.input().syntax().typescript() && p.input_mut().eat(&P::Token::QUESTION); - return make_property( - p, - start, - decorators, - accessibility, - key, - false, - accessor_token, - is_optional, - false, - declare, - false, - false, - ); - } - } else { - // TODO: error if static contains escape - } - } - - parse_class_member_with_is_static( - p, - start, - declare_token, - accessibility, - static_token, - accessor_token, - decorators, - ) -} - -fn parse_class_body<'a, P: Parser<'a>>(p: &mut P) -> PResult> { - let mut elems = Vec::with_capacity(32); - let mut has_constructor_with_body = false; - while !p.input().is(&P::Token::RBRACE) { - if p.input_mut().eat(&P::Token::SEMI) { - let span = p.input().prev_span(); - debug_assert!(span.lo <= span.hi); - elems.push(ClassMember::Empty(EmptyStmt { span })); - continue; - } - let elem = p.do_inside_of_context(Context::AllowDirectSuper, parse_class_member)?; - - if !p.ctx().contains(Context::InDeclare) { - if let ClassMember::Constructor(Constructor { - body: Some(..), - span, - .. - }) = elem - { - if has_constructor_with_body { - p.emit_err(span, SyntaxError::DuplicateConstructor); - } - has_constructor_with_body = true; - } - } - elems.push(elem); - } - Ok(elems) -} - -pub fn parse_class<'a, T>( - p: &mut impl Parser<'a>, - start: BytePos, - class_start: BytePos, - decorators: Vec, - is_abstract: bool, -) -> PResult -where - T: OutputType, -{ - let (ident, mut class) = p.do_inside_of_context(Context::InClass, |p| { - parse_class_inner(p, start, class_start, decorators, T::IS_IDENT_REQUIRED) - })?; - - if is_abstract { - class.is_abstract = true - } else { - for member in class.body.iter() { - match member { - ClassMember::ClassProp(ClassProp { - is_abstract: true, - span, - .. - }) - | ClassMember::Method(ClassMethod { - span, - is_abstract: true, - .. - }) => p.emit_err(*span, SyntaxError::TS1244), - _ => (), - } - } - } - - match T::finish_class(p.span(start), ident, class) { - Ok(v) => Ok(v), - Err(kind) => syntax_error!(p, kind), - } -} - -/// Not generic -fn parse_class_inner<'a, P: Parser<'a>>( - p: &mut P, - _start: BytePos, - class_start: BytePos, - decorators: Vec, - is_ident_required: bool, -) -> PResult<(Option, Box)> { - p.strict_mode(|p| { - expect!(p, &P::Token::CLASS); - - let ident = parse_maybe_opt_binding_ident(p, is_ident_required, true)?; - if p.input().syntax().typescript() { - if let Some(span) = ident.invalid_class_name() { - p.emit_err(span, SyntaxError::TS2414); - } - } - - let type_params = if p.input().syntax().typescript() { - try_parse_ts_type_params(p, true, true)? - } else { - None - }; - - let (mut super_class, mut super_type_params) = if p.input_mut().eat(&P::Token::EXTENDS) { - let (super_class, super_type_params) = parse_super_class(p)?; - - if p.syntax().typescript() && p.input_mut().eat(&P::Token::COMMA) { - let exprs = parse_ts_heritage_clause(p)?; - - for e in &exprs { - p.emit_err(e.span(), SyntaxError::TS1174); - } - } - - (Some(super_class), super_type_params) - } else { - (None, None) - }; - - // Handle TS1172 - if p.input_mut().eat(&P::Token::EXTENDS) { - p.emit_err(p.input().prev_span(), SyntaxError::TS1172); - - parse_super_class(p)?; - }; - - let implements = - if p.input().syntax().typescript() && p.input_mut().eat(&P::Token::IMPLEMENTS) { - parse_ts_heritage_clause(p)? - } else { - Vec::with_capacity(4) - }; - - { - // Handle TS1175 - if p.input().syntax().typescript() && p.input_mut().eat(&P::Token::IMPLEMENTS) { - p.emit_err(p.input().prev_span(), SyntaxError::TS1175); - - parse_ts_heritage_clause(p)?; - } - } - - // Handle TS1173 - if p.input().syntax().typescript() && p.input_mut().eat(&P::Token::EXTENDS) { - p.emit_err(p.input().prev_span(), SyntaxError::TS1173); - - let (sc, type_params) = parse_super_class(p)?; - - if super_class.is_none() { - super_class = Some(sc); - if type_params.is_some() { - super_type_params = type_params; - } - } - } - - expect!(p, &P::Token::LBRACE); - - let body = if super_class.is_some() { - p.do_inside_of_context(Context::HasSuperClass, parse_class_body)? - } else { - p.do_outside_of_context(Context::HasSuperClass, parse_class_body)? - }; - - if p.input().cur().is_eof() { - let eof_text = p.input_mut().dump_cur(); - p.emit_err( - p.input().cur_span(), - SyntaxError::Expected(format!("{:?}", P::Token::RBRACE), eof_text), - ); - } else { - expect!(p, &P::Token::RBRACE); - } - - let span = p.span(class_start); - Ok(( - ident, - Box::new(Class { - span, - decorators, - is_abstract: false, - type_params, - super_class, - super_type_params, - body, - implements, - ..Default::default() - }), - )) - }) -} diff --git a/crates/swc_ecma_lexer/src/common/parser/expr.rs b/crates/swc_ecma_lexer/src/common/parser/expr.rs deleted file mode 100644 index 7bcb674d7698..000000000000 --- a/crates/swc_ecma_lexer/src/common/parser/expr.rs +++ /dev/null @@ -1,2619 +0,0 @@ -use either::Either; -use rustc_hash::FxHashMap; -use swc_atoms::atom; -use swc_common::{util::take::Take, BytePos, Span, Spanned}; -use swc_ecma_ast::*; - -use super::{ - assign_target_or_spread::AssignTargetOrSpread, buffer::Buffer, ident::parse_ident_name, - PResult, Parser, -}; -use crate::{ - common::{ - context::Context, - lexer::token::TokenFactory, - parser::{ - class_and_fn::{ - parse_async_fn_expr, parse_class_expr, parse_decorators, - parse_fn_block_or_expr_body, parse_fn_expr, - }, - eof_error, - expr_ext::ExprExt, - ident::{parse_binding_ident, parse_ident, parse_maybe_private_name}, - is_simple_param_list::IsSimpleParameterList, - jsx::{parse_jsx_element, parse_jsx_text}, - object::parse_object_expr, - pat::{parse_paren_items_as_params, reparse_expr_as_pat}, - pat_type::PatType, - token_and_span::TokenAndSpan, - typescript::*, - unwrap_ts_non_null, - }, - }, - error::{Error, SyntaxError}, - TokenContext, -}; - -pub(super) fn is_start_of_left_hand_side_expr<'a>(p: &mut impl Parser<'a>) -> bool { - let cur = p.input().cur(); - cur.is_this() - || cur.is_null() - || cur.is_super() - || cur.is_true() - || cur.is_false() - || cur.is_num() - || cur.is_bigint() - || cur.is_str() - || cur.is_no_substitution_template_literal() - || cur.is_template_head() - || cur.is_lparen() - || cur.is_lbrace() - || cur.is_lbracket() - || cur.is_function() - || cur.is_class() - || cur.is_new() - || cur.is_regexp() - || cur.is_import() - || cur.is_ident_ref(p.ctx()) - || cur.is_backquote() && { - peek!(p).is_some_and(|peek| peek.is_lparen() || peek.is_less() || peek.is_dot()) - } -} - -#[cfg_attr( - all(debug_assertions, feature = "tracing-spans"), - tracing::instrument(level = "debug", skip_all) -)] -pub fn parse_array_lit<'a, P: Parser<'a>>(p: &mut P) -> PResult> { - trace_cur!(p, parse_array_lit); - - let start = p.input().cur_pos(); - - p.assert_and_bump(&P::Token::LBRACKET); - - let mut elems = Vec::with_capacity(8); - - while !p.input().is(&P::Token::RBRACKET) { - if p.input().is(&P::Token::COMMA) { - expect!(p, &P::Token::COMMA); - elems.push(None); - continue; - } - - elems.push(p.allow_in_expr(|p| p.parse_expr_or_spread()).map(Some)?); - - if !p.input().is(&P::Token::RBRACKET) { - expect!(p, &P::Token::COMMA); - if p.input().is(&P::Token::RBRACKET) { - let prev_span = p.input().prev_span(); - p.state_mut().trailing_commas.insert(start, prev_span); - } - } - } - - expect!(p, &P::Token::RBRACKET); - - let span = p.span(start); - Ok(ArrayLit { span, elems }.into()) -} - -pub fn at_possible_async<'a, P: Parser<'a>>(p: &P, expr: &Expr) -> bool { - // TODO(kdy1): !this.state.containsEsc && - p.state().potential_arrow_start == Some(expr.span_lo()) && expr.is_ident_ref_to("async") -} - -fn parse_yield_expr<'a, P: Parser<'a>>(p: &mut P) -> PResult> { - let start = p.input().cur_pos(); - p.assert_and_bump(&P::Token::YIELD); - debug_assert!(p.ctx().contains(Context::InGenerator)); - - // Spec says - // YieldExpression cannot be used within the FormalParameters of a generator - // function because any expressions that are part of FormalParameters are - // evaluated before the resulting generator object is in a resumable state. - if p.ctx().contains(Context::InParameters) && !p.ctx().contains(Context::InFunction) { - syntax_error!(p, p.input().prev_span(), SyntaxError::YieldParamInGen) - } - - let parse_with_arg = |p: &mut P| { - let has_star = p.input_mut().eat(&P::Token::MUL); - let err_span = p.span(start); - let arg = parse_assignment_expr(p).map_err(|err| { - Error::new( - err.span(), - SyntaxError::WithLabel { - inner: Box::new(err), - span: err_span, - note: "Tried to parse an argument of yield", - }, - ) - })?; - Ok(YieldExpr { - span: p.span(start), - arg: Some(arg), - delegate: has_star, - } - .into()) - }; - - if p.is_general_semi() || { - let cur = p.input().cur(); - !cur.is_less() - && !cur.is_star() - && !cur.is_slash() - && !cur.is_slash_eq() - && !cur.starts_expr() - } { - Ok(YieldExpr { - span: p.span(start), - arg: None, - delegate: false, - } - .into()) - } else { - parse_with_arg(p) - } -} - -fn parse_tpl_elements<'a, P: Parser<'a>>( - p: &mut P, - is_tagged_tpl: bool, -) -> PResult<(Vec>, Vec)> { - trace_cur!(p, parse_tpl_elements); - - let mut exprs = Vec::new(); - - let cur_elem = p.parse_tpl_element(is_tagged_tpl)?; - let mut is_tail = cur_elem.tail; - let mut quasis = vec![cur_elem]; - - while !is_tail { - expect!(p, &P::Token::DOLLAR_LBRACE); - exprs.push(p.allow_in_expr(|p| p.parse_expr())?); - expect!(p, &P::Token::RBRACE); - let elem = p.parse_tpl_element(is_tagged_tpl)?; - is_tail = elem.tail; - quasis.push(elem); - } - - Ok((exprs, quasis)) -} - -fn parse_tpl<'a, P: Parser<'a>>(p: &mut P, is_tagged_tpl: bool) -> PResult { - trace_cur!(p, parse_tpl); - let start = p.input().cur_pos(); - - p.assert_and_bump(&P::Token::BACKQUOTE); - - let (exprs, quasis) = parse_tpl_elements(p, is_tagged_tpl)?; - - expect!(p, &P::Token::BACKQUOTE); - - let span = p.span(start); - Ok(Tpl { - span, - exprs, - quasis, - }) -} - -pub(crate) fn parse_tagged_tpl<'a, P: Parser<'a>>( - p: &mut P, - tag: Box, - type_params: Option>, -) -> PResult { - let tagged_tpl_start = tag.span_lo(); - trace_cur!(p, parse_tagged_tpl); - - let tpl = Box::new(parse_tpl(p, true)?); - - let span = p.span(tagged_tpl_start); - - if tag.is_opt_chain() { - p.emit_err(span, SyntaxError::TaggedTplInOptChain); - } - - Ok(TaggedTpl { - span, - tag, - type_params, - tpl, - ..Default::default() - }) -} - -pub fn parse_str_lit<'a>(p: &mut impl Parser<'a>) -> swc_ecma_ast::Str { - debug_assert!(p.input().cur().is_str()); - let token_and_span = p.input().get_cur(); - let start = token_and_span.span().lo; - let (value, raw) = p.input_mut().expect_string_token_and_bump(); - swc_ecma_ast::Str { - span: p.span(start), - value, - raw: Some(raw), - } -} - -pub fn parse_lit<'a, P: Parser<'a>>(p: &mut P) -> PResult { - let token_and_span = p.input().get_cur(); - let start = token_and_span.span().lo; - let cur = token_and_span.token(); - let v = if cur.is_null() { - p.bump(); - let span = p.span(start); - Lit::Null(swc_ecma_ast::Null { span }) - } else if cur.is_true() || cur.is_false() { - let value = cur.is_true(); - p.bump(); - let span = p.span(start); - Lit::Bool(swc_ecma_ast::Bool { span, value }) - } else if cur.is_str() { - Lit::Str(parse_str_lit(p)) - } else if cur.is_num() { - let (value, raw) = p.input_mut().expect_number_token_and_bump(); - Lit::Num(swc_ecma_ast::Number { - span: p.span(start), - value, - raw: Some(raw), - }) - } else if cur.is_bigint() { - let (value, raw) = p.input_mut().expect_bigint_token_and_bump(); - Lit::BigInt(swc_ecma_ast::BigInt { - span: p.span(start), - value, - raw: Some(raw), - }) - } else if cur.is_error() { - let err = p.input_mut().expect_error_token_and_bump(); - return Err(err); - } else if cur.is_eof() { - return Err(eof_error(p)); - } else { - unreachable!("parse_lit should not be called for {:?}", cur) - }; - Ok(v) -} - -/// Parse `Arguments[Yield, Await]` -#[cfg_attr( - all(debug_assertions, feature = "tracing-spans"), - tracing::instrument(skip_all) -)] -pub fn parse_args<'a, P: Parser<'a>>( - p: &mut P, - is_dynamic_import: bool, -) -> PResult> { - trace_cur!(p, parse_args); - - p.do_outside_of_context(Context::WillExpectColonForCond, |p| { - let start = p.cur_pos(); - expect!(p, &P::Token::LPAREN); - - let mut first = true; - let mut expr_or_spreads = Vec::with_capacity(2); - - while !p.input().is(&P::Token::RPAREN) { - if first { - first = false; - } else { - expect!(p, &P::Token::COMMA); - // Handle trailing comma. - if p.input().is(&P::Token::RPAREN) { - if is_dynamic_import && !p.input().syntax().import_attributes() { - syntax_error!(p, p.span(start), SyntaxError::TrailingCommaInsideImport) - } - - break; - } - } - - expr_or_spreads.push(p.allow_in_expr(|p| p.parse_expr_or_spread())?); - } - - expect!(p, &P::Token::RPAREN); - Ok(expr_or_spreads) - }) -} - -///`parseMaybeAssign` (overridden) -#[cfg_attr( - all(debug_assertions, feature = "tracing-spans"), - tracing::instrument(level = "debug", skip_all) -)] -pub fn parse_assignment_expr<'a, P: Parser<'a>>(p: &mut P) -> PResult> { - trace_cur!(p, parse_assignment_expr); - - if p.input().syntax().typescript() && p.input().is(&P::Token::JSX_TAG_START) { - // Note: When the JSX plugin is on, type assertions (` x`) aren't valid - // syntax. - - let cur_context = p.input().token_context().current(); - debug_assert_eq!(cur_context, Some(TokenContext::JSXOpeningTag)); - // Only time j_oTag is pushed is right after j_expr. - debug_assert_eq!( - p.input().token_context().0[p.input().token_context().len() - 2], - TokenContext::JSXExpr - ); - - let res = try_parse_ts(p, |p| parse_assignment_expr_base(p).map(Some)); - if let Some(res) = res { - return Ok(res); - } else { - debug_assert_eq!( - p.input_mut().token_context().current(), - Some(TokenContext::JSXOpeningTag) - ); - p.input_mut().token_context_mut().pop(); - debug_assert_eq!( - p.input_mut().token_context().current(), - Some(TokenContext::JSXExpr) - ); - p.input_mut().token_context_mut().pop(); - } - } - - parse_assignment_expr_base(p) -} - -/// Parse an assignment expression. This includes applications of -/// operators like `+=`. -/// -/// `parseMaybeAssign` -#[cfg_attr( - all(debug_assertions, feature = "tracing-spans"), - tracing::instrument(skip_all) -)] -fn parse_assignment_expr_base<'a, P: Parser<'a>>(p: &mut P) -> PResult> { - trace_cur!(p, parse_assignment_expr_base); - let start = p.input().cur_span(); - - if p.input().syntax().typescript() - && (p.input().cur().is_less() || p.input().cur().is_jsx_tag_start()) - && (peek!(p).is_some_and(|peek| peek.is_word() || peek.is_jsx_name())) - { - let res = p.do_outside_of_context(Context::WillExpectColonForCond, |p| { - try_parse_ts(p, |p| { - if p.input().cur().is_jsx_tag_start() { - if let Some(TokenContext::JSXOpeningTag) = - p.input_mut().token_context().current() - { - p.input_mut().token_context_mut().pop(); - - debug_assert_eq!( - p.input_mut().token_context().current(), - Some(TokenContext::JSXExpr) - ); - p.input_mut().token_context_mut().pop(); - } - } - - let type_parameters = parse_ts_type_params(p, false, true)?; - - // In TSX mode, type parameters that could be mistaken for JSX - // (single param without constraint and no trailing comma) are not - // allowed. e.g., `() => {}` is invalid in TSX because `` - // looks like JSX. - // Valid alternatives: `() => {}` or `() => - // {}` - if p.input().syntax().jsx() - && !p.input().syntax().flow() - && type_parameters.params.len() == 1 - { - let single_param = &type_parameters.params[0]; - let has_trailing_comma = type_parameters.span.hi.0 - single_param.span.hi.0 > 1; - let dominated_by_jsx = single_param.constraint.is_none() - && single_param.default.is_none() - && !has_trailing_comma; - - if dominated_by_jsx { - return Ok(None); - } - } - - let mut arrow = parse_assignment_expr_base(p)?; - match *arrow { - Expr::Arrow(ArrowExpr { - ref mut span, - ref mut type_params, - .. - }) => { - *span = Span::new_with_checked(type_parameters.span.lo, span.hi); - *type_params = Some(type_parameters); - } - _ => unexpected!(p, "("), - } - Ok(Some(arrow)) - }) - }); - if let Some(res) = res { - if p.input().syntax().disallow_ambiguous_jsx_like() { - p.emit_err(start, SyntaxError::ReservedArrowTypeParam); - } - return Ok(res); - } - } - - if p.ctx().contains(Context::InGenerator) && p.input().is(&P::Token::YIELD) { - return parse_yield_expr(p); - } - - let cur = p.input().cur(); - - if cur.is_error() { - let err = p.input_mut().expect_error_token_and_bump(); - return Err(err); - } - - p.state_mut().potential_arrow_start = - if cur.is_known_ident() || cur.is_unknown_ident() || cur.is_yield() || cur.is_lparen() { - Some(p.cur_pos()) - } else { - None - }; - - let start = p.cur_pos(); - - // Try to parse conditional expression. - let cond = parse_cond_expr(p)?; - - return_if_arrow!(p, cond); - - match *cond { - // if cond is conditional expression but not left-hand-side expression, - // just return it. - Expr::Cond(..) | Expr::Bin(..) | Expr::Unary(..) | Expr::Update(..) => return Ok(cond), - _ => {} - } - - finish_assignment_expr(p, start, cond) -} - -pub fn finish_assignment_expr<'a, P: Parser<'a>>( - p: &mut P, - start: BytePos, - cond: Box, -) -> PResult> { - trace_cur!(p, finish_assignment_expr); - - if let Some(op) = p.input().cur().as_assign_op() { - let left = if op == AssignOp::Assign { - match AssignTarget::try_from(reparse_expr_as_pat(p, PatType::AssignPat, cond)?) { - Ok(pat) => pat, - Err(expr) => { - syntax_error!(p, expr.span(), SyntaxError::InvalidAssignTarget) - } - } - } else { - // It is an early Reference Error if IsValidSimpleAssignmentTarget of - // LeftHandSideExpression is false. - if !cond.is_valid_simple_assignment_target(p.ctx().contains(Context::Strict)) { - if p.input().syntax().typescript() { - p.emit_err(cond.span(), SyntaxError::TS2406); - } else { - p.emit_err(cond.span(), SyntaxError::NotSimpleAssign) - } - } - if p.input().syntax().typescript() - && cond - .as_ident() - .map(|i| i.is_reserved_in_strict_bind()) - .unwrap_or(false) - { - p.emit_strict_mode_err(cond.span(), SyntaxError::TS1100); - } - - // TODO - match AssignTarget::try_from(cond) { - Ok(v) => v, - Err(v) => { - syntax_error!(p, v.span(), SyntaxError::InvalidAssignTarget); - } - } - }; - - p.bump(); - let right = parse_assignment_expr(p)?; - Ok(AssignExpr { - span: p.span(start), - op, - // TODO: - left, - right, - } - .into()) - } else { - Ok(cond) - } -} - -/// Spec: 'ConditionalExpression' -#[cfg_attr( - all(debug_assertions, feature = "tracing-spans"), - tracing::instrument(level = "debug", skip_all) -)] -fn parse_cond_expr<'a, P: Parser<'a>>(p: &mut P) -> PResult> { - trace_cur!(p, parse_cond_expr); - - let start = p.cur_pos(); - - let test = parse_bin_expr(p)?; - return_if_arrow!(p, test); - - if p.input_mut().eat(&P::Token::QUESTION) { - let cons = p.do_inside_of_context( - Context::InCondExpr - .union(Context::WillExpectColonForCond) - .union(Context::IncludeInExpr), - parse_assignment_expr, - )?; - - expect!(p, &P::Token::COLON); - - let alt = p.do_inside_of_context(Context::InCondExpr, |p| { - p.do_outside_of_context(Context::WillExpectColonForCond, parse_assignment_expr) - })?; - - let span = Span::new_with_checked(start, alt.span_hi()); - Ok(CondExpr { - span, - test, - cons, - alt, - } - .into()) - } else { - Ok(test) - } -} - -#[cfg_attr( - all(debug_assertions, feature = "tracing-spans"), - tracing::instrument(skip_all) -)] -pub fn parse_subscripts<'a>( - p: &mut impl Parser<'a>, - mut obj: Callee, - no_call: bool, - no_computed_member: bool, -) -> PResult> { - let start = obj.span().lo; - loop { - obj = match parse_subscript(p, start, obj, no_call, no_computed_member)? { - (expr, false) => return Ok(expr), - (expr, true) => Callee::Expr(expr), - } - } -} - -/// returned bool is true if this method should be called again. -#[cfg_attr( - all(debug_assertions, feature = "tracing-spans"), - tracing::instrument(skip_all) -)] -fn parse_subscript<'a, P: Parser<'a>>( - p: &mut P, - start: BytePos, - mut obj: Callee, - no_call: bool, - no_computed_member: bool, -) -> PResult<(Box, bool)> { - trace_cur!(p, parse_subscript); - - if p.input().syntax().typescript() { - if !p.input().had_line_break_before_cur() && p.input().is(&P::Token::BANG) { - p.input_mut().set_expr_allowed(false); - p.assert_and_bump(&P::Token::BANG); - - let expr = match obj { - Callee::Super(..) => { - syntax_error!( - p, - p.input().cur_span(), - SyntaxError::TsNonNullAssertionNotAllowed(atom!("super")) - ) - } - Callee::Import(..) => { - syntax_error!( - p, - p.input().cur_span(), - SyntaxError::TsNonNullAssertionNotAllowed(atom!("import")) - ) - } - Callee::Expr(expr) => expr, - #[cfg(swc_ast_unknown)] - _ => unreachable!(), - }; - return Ok(( - TsNonNullExpr { - span: p.span(start), - expr, - } - .into(), - true, - )); - } - - if matches!(obj, Callee::Expr(..)) && p.input().is(&P::Token::LESS) { - let is_dynamic_import = obj.is_import(); - - let mut obj_opt = Some(obj); - // tsTryParseAndCatch is expensive, so avoid if not necessary. - // There are number of things we are going to "maybe" parse, like type arguments - // on tagged template expressions. If any of them fail, walk it back and - // continue. - - let mut_obj_opt = &mut obj_opt; - - let result = p.do_inside_of_context(Context::ShouldNotLexLtOrGtAsType, |p| { - try_parse_ts(p, |p| { - if !no_call - && at_possible_async( - p, - match &mut_obj_opt { - Some(Callee::Expr(ref expr)) => expr, - _ => unreachable!(), - }, - ) - { - // Almost certainly this is a generic async function `async () => ... - // But it might be a call with a type argument `async();` - let async_arrow_fn = try_parse_ts_generic_async_arrow_fn(p, start)?; - if let Some(async_arrow_fn) = async_arrow_fn { - return Ok(Some((async_arrow_fn.into(), true))); - } - } - - let type_args = parse_ts_type_args(p)?; - p.assert_and_bump(&P::Token::GREATER); - let cur = p.input().cur(); - - if !no_call && cur.is_lparen() { - // possibleAsync always false here, because we would have handled it - // above. (won't be any undefined arguments) - let args = parse_args(p, is_dynamic_import)?; - - let obj = mut_obj_opt.take().unwrap(); - - if let Callee::Expr(callee) = &obj { - if let Expr::OptChain(..) = &**callee { - return Ok(Some(( - OptChainExpr { - span: p.span(start), - base: Box::new(OptChainBase::Call(OptCall { - span: p.span(start), - callee: obj.expect_expr(), - type_args: Some(type_args), - args, - ..Default::default() - })), - optional: false, - } - .into(), - true, - ))); - } - } - - Ok(Some(( - CallExpr { - span: p.span(start), - callee: obj, - type_args: Some(type_args), - args, - ..Default::default() - } - .into(), - true, - ))) - } else if cur.is_no_substitution_template_literal() - || cur.is_template_head() - || cur.is_backquote() - { - p.parse_tagged_tpl( - match mut_obj_opt { - Some(Callee::Expr(obj)) => obj.take(), - _ => unreachable!(), - }, - Some(type_args), - ) - .map(|expr| (expr.into(), true)) - .map(Some) - } else if cur.is_equal() || cur.is_as() || cur.is_satisfies() { - Ok(Some(( - TsInstantiation { - span: p.span(start), - expr: match mut_obj_opt { - Some(Callee::Expr(obj)) => obj.take(), - _ => unreachable!(), - }, - type_args, - } - .into(), - false, - ))) - } else if no_call { - unexpected!(p, "`") - } else { - unexpected!(p, "( or `") - } - }) - }); - if let Some(result) = result { - return Ok(result); - } - - obj = obj_opt.unwrap(); - } - } - - let type_args = if p.syntax().typescript() && p.input().is(&P::Token::LESS) { - try_parse_ts_type_args(p) - } else { - None - }; - - let cur = p.input().cur(); - - if obj.is_import() && !(cur.is_dot() || cur.is_lparen()) { - unexpected!(p, "`.` or `(`") - } - - let question_dot_token = - if p.input().is(&P::Token::QUESTION) && peek!(p).is_some_and(|peek| peek.is_dot()) { - let start = p.cur_pos(); - expect!(p, &P::Token::QUESTION); - Some(p.span(start)) - } else { - None - }; - - // $obj[name()] - if !no_computed_member - && ((question_dot_token.is_some() - && p.input().is(&P::Token::DOT) - && peek!(p).is_some_and(|peek| peek.is_lbracket()) - && p.input_mut().eat(&P::Token::DOT) - && p.input_mut().eat(&P::Token::LBRACKET)) - || p.input_mut().eat(&P::Token::LBRACKET)) - { - let bracket_lo = p.input().prev_span().lo; - let prop = p.allow_in_expr(|p| p.parse_expr())?; - expect!(p, &P::Token::RBRACKET); - let span = Span::new_with_checked(obj.span_lo(), p.input().last_pos()); - debug_assert_eq!(obj.span_lo(), span.lo()); - let prop = ComputedPropName { - span: Span::new_with_checked(bracket_lo, p.input().last_pos()), - expr: prop, - }; - - let type_args = if p.syntax().typescript() && p.input().is(&P::Token::LESS) { - try_parse_ts_type_args(p) - } else { - None - }; - - return Ok(( - Box::new(match obj { - Callee::Import(..) => unreachable!(), - Callee::Super(obj) => { - if !p.ctx().contains(Context::AllowDirectSuper) - && !p.input().syntax().allow_super_outside_method() - { - syntax_error!(p, obj.span, SyntaxError::InvalidSuper); - } else if question_dot_token.is_some() { - if no_call { - syntax_error!(p, obj.span, SyntaxError::InvalidSuperCall); - } else { - syntax_error!(p, obj.span, SyntaxError::InvalidSuper); - } - } else { - SuperPropExpr { - span, - obj, - prop: SuperProp::Computed(prop), - } - .into() - } - } - Callee::Expr(obj) => { - let is_opt_chain = unwrap_ts_non_null(&obj).is_opt_chain(); - let expr = MemberExpr { - span, - obj, - prop: MemberProp::Computed(prop), - }; - let expr = if is_opt_chain || question_dot_token.is_some() { - OptChainExpr { - span, - optional: question_dot_token.is_some(), - base: Box::new(OptChainBase::Member(expr)), - } - .into() - } else { - expr.into() - }; - - if let Some(type_args) = type_args { - TsInstantiation { - expr: Box::new(expr), - type_args, - span: p.span(start), - } - .into() - } else { - expr - } - } - #[cfg(swc_ast_unknown)] - _ => unreachable!(), - }), - true, - )); - } - - if (question_dot_token.is_some() - && p.input().is(&P::Token::DOT) - && (peek!(p).is_some_and(|peek| peek.is_lparen()) - || (p.syntax().typescript() && peek!(p).is_some_and(|peek| peek.is_less()))) - && p.input_mut().eat(&P::Token::DOT)) - || (!no_call && p.input().is(&P::Token::LPAREN)) - { - let type_args = if p.syntax().typescript() && p.input().is(&P::Token::LESS) { - let ret = parse_ts_type_args(p)?; - p.assert_and_bump(&P::Token::GREATER); - Some(ret) - } else { - None - }; - let args = parse_args(p, obj.is_import())?; - let span = p.span(start); - return if question_dot_token.is_some() - || match &obj { - Callee::Expr(obj) => unwrap_ts_non_null(obj).is_opt_chain(), - _ => false, - } { - match obj { - Callee::Super(_) | Callee::Import(_) => { - syntax_error!(p, p.input().cur_span(), SyntaxError::SuperCallOptional) - } - Callee::Expr(callee) => Ok(( - OptChainExpr { - span, - optional: question_dot_token.is_some(), - base: Box::new(OptChainBase::Call(OptCall { - span: p.span(start), - callee, - args, - type_args, - ..Default::default() - })), - } - .into(), - true, - )), - #[cfg(swc_ast_unknown)] - _ => unreachable!(), - } - } else { - Ok(( - CallExpr { - span: p.span(start), - callee: obj, - args, - ..Default::default() - } - .into(), - true, - )) - }; - } - - // member expression - // $obj.name - if p.input_mut().eat(&P::Token::DOT) { - let prop = parse_maybe_private_name(p).map(|e| match e { - Either::Left(p) => MemberProp::PrivateName(p), - Either::Right(i) => MemberProp::Ident(i), - })?; - let span = p.span(obj.span_lo()); - debug_assert_eq!(obj.span_lo(), span.lo()); - debug_assert_eq!(prop.span_hi(), span.hi()); - - let type_args = if p.syntax().typescript() && p.input().is(&P::Token::LESS) { - try_parse_ts_type_args(p) - } else { - None - }; - - return Ok(( - Box::new(match obj { - callee @ Callee::Import(_) => match prop { - MemberProp::Ident(IdentName { sym, .. }) => { - if !p.ctx().contains(Context::CanBeModule) { - let span = p.span(start); - p.emit_err(span, SyntaxError::ImportMetaInScript); - } - match &*sym { - "meta" => MetaPropExpr { - span, - kind: MetaPropKind::ImportMeta, - } - .into(), - _ => { - let args = parse_args(p, true)?; - - CallExpr { - span, - callee, - args, - type_args: None, - ..Default::default() - } - .into() - } - } - } - _ => { - unexpected!(p, "meta"); - } - }, - Callee::Super(obj) => { - if !p.ctx().contains(Context::AllowDirectSuper) - && !p.input().syntax().allow_super_outside_method() - { - syntax_error!(p, obj.span, SyntaxError::InvalidSuper); - } else if question_dot_token.is_some() { - if no_call { - syntax_error!(p, obj.span, SyntaxError::InvalidSuperCall); - } else { - syntax_error!(p, obj.span, SyntaxError::InvalidSuper); - } - } else { - match prop { - MemberProp::Ident(ident) => SuperPropExpr { - span, - obj, - prop: SuperProp::Ident(ident), - } - .into(), - MemberProp::PrivateName(..) => { - syntax_error!( - p, - p.input().cur_span(), - SyntaxError::InvalidSuperCall - ) - } - MemberProp::Computed(..) => unreachable!(), - #[cfg(swc_ast_unknown)] - _ => unreachable!(), - } - } - } - Callee::Expr(obj) => { - let expr = MemberExpr { span, obj, prop }; - let expr = if unwrap_ts_non_null(&expr.obj).is_opt_chain() - || question_dot_token.is_some() - { - OptChainExpr { - span: p.span(start), - optional: question_dot_token.is_some(), - base: Box::new(OptChainBase::Member(expr)), - } - .into() - } else { - expr.into() - }; - if let Some(type_args) = type_args { - TsInstantiation { - expr: Box::new(expr), - type_args, - span: p.span(start), - } - .into() - } else { - expr - } - } - #[cfg(swc_ast_unknown)] - _ => unreachable!(), - }), - true, - )); - } - - match obj { - Callee::Expr(expr) => { - let expr = if let Some(type_args) = type_args { - TsInstantiation { - expr, - type_args, - span: p.span(start), - } - .into() - } else { - expr - }; - - // MemberExpression[?Yield, ?Await] TemplateLiteral[?Yield, ?Await, +Tagged] - let cur = p.input().cur(); - if cur.is_template_head() - || cur.is_no_substitution_template_literal() - || cur.is_backquote() - { - let tpl = p.do_outside_of_context(Context::WillExpectColonForCond, |p| { - p.parse_tagged_tpl(expr, None) - })?; - return Ok((tpl.into(), true)); - } - - Ok((expr, false)) - } - Callee::Super(..) => { - if no_call { - syntax_error!(p, p.input().cur_span(), SyntaxError::InvalidSuperCall); - } - syntax_error!(p, p.input().cur_span(), SyntaxError::InvalidSuper); - } - Callee::Import(..) => { - syntax_error!(p, p.input().cur_span(), SyntaxError::InvalidImport); - } - #[cfg(swc_ast_unknown)] - _ => unreachable!(), - } -} - -pub fn parse_dynamic_import_or_import_meta<'a, P: Parser<'a>>( - p: &mut P, - start: BytePos, - no_call: bool, -) -> PResult> { - if p.input_mut().eat(&P::Token::DOT) { - p.mark_found_module_item(); - - let ident = parse_ident_name(p)?; - - match &*ident.sym { - "meta" => { - let span = p.span(start); - if !p.ctx().contains(Context::CanBeModule) { - p.emit_err(span, SyntaxError::ImportMetaInScript); - } - let expr = MetaPropExpr { - span, - kind: MetaPropKind::ImportMeta, - }; - parse_subscripts(p, Callee::Expr(expr.into()), no_call, false) - } - "defer" => parse_dynamic_import_call(p, start, no_call, ImportPhase::Defer), - "source" => parse_dynamic_import_call(p, start, no_call, ImportPhase::Source), - _ => unexpected!(p, "meta"), - } - } else { - parse_dynamic_import_call(p, start, no_call, ImportPhase::Evaluation) - } -} - -fn parse_dynamic_import_call<'a>( - p: &mut impl Parser<'a>, - start: BytePos, - no_call: bool, - phase: ImportPhase, -) -> PResult> { - let import = Callee::Import(Import { - span: p.span(start), - phase, - }); - - parse_subscripts(p, import, no_call, false) -} - -/// `is_new_expr`: true iff we are parsing production 'NewExpression'. -#[cfg_attr( - all(debug_assertions, feature = "tracing-spans"), - tracing::instrument(level = "debug", skip_all) -)] -pub fn parse_member_expr_or_new_expr<'a>( - p: &mut impl Parser<'a>, - is_new_expr: bool, -) -> PResult> { - p.do_inside_of_context(Context::ShouldNotLexLtOrGtAsType, |p| { - parse_member_expr_or_new_expr_inner(p, is_new_expr) - }) -} - -fn parse_member_expr_or_new_expr_inner<'a, P: Parser<'a>>( - p: &mut P, - is_new_expr: bool, -) -> PResult> { - trace_cur!(p, parse_member_expr_or_new_expr); - - let start = p.cur_pos(); - if p.input_mut().eat(&P::Token::NEW) { - if p.input_mut().eat(&P::Token::DOT) { - if p.input_mut().eat(&P::Token::TARGET) { - let span = p.span(start); - let expr = MetaPropExpr { - span, - kind: MetaPropKind::NewTarget, - } - .into(); - - let ctx = p.ctx(); - if !ctx.contains(Context::InsideNonArrowFunctionScope) - && !ctx.contains(Context::InParameters) - && !ctx.contains(Context::InClass) - { - p.emit_err(span, SyntaxError::InvalidNewTarget); - } - - return parse_subscripts(p, Callee::Expr(expr), true, false); - } - - unexpected!(p, "target") - } - - // 'NewExpression' allows new call without paren. - let callee = parse_member_expr_or_new_expr(p, is_new_expr)?; - return_if_arrow!(p, callee); - - if is_new_expr { - match *callee { - Expr::OptChain(OptChainExpr { - span, - optional: true, - .. - }) => { - syntax_error!(p, span, SyntaxError::OptChainCannotFollowConstructorCall) - } - Expr::Member(MemberExpr { ref obj, .. }) => { - if let Expr::OptChain(OptChainExpr { - span, - optional: true, - .. - }) = **obj - { - syntax_error!(p, span, SyntaxError::OptChainCannotFollowConstructorCall) - } - } - _ => {} - } - } - - let type_args = if p.input().syntax().typescript() && { - let cur = p.input().cur(); - cur.is_less() || cur.is_lshift() - } { - try_parse_ts(p, |p| { - let args = - p.do_outside_of_context(Context::ShouldNotLexLtOrGtAsType, parse_ts_type_args)?; - p.assert_and_bump(&P::Token::GREATER); - if !p.input().is(&P::Token::LPAREN) { - let span = p.input().cur_span(); - let cur = p.input_mut().dump_cur(); - syntax_error!(p, span, SyntaxError::Expected('('.to_string(), cur)) - } - Ok(Some(args)) - }) - } else { - None - }; - - if !is_new_expr || p.input().is(&P::Token::LPAREN) { - // Parsed with 'MemberExpression' production. - let args = parse_args(p, false).map(Some)?; - - let new_expr = Callee::Expr( - NewExpr { - span: p.span(start), - callee, - args, - type_args, - ..Default::default() - } - .into(), - ); - - // We should parse subscripts for MemberExpression. - // Because it's left recursive. - return parse_subscripts(p, new_expr, true, false); - } - - // Parsed with 'NewExpression' production. - - return Ok(NewExpr { - span: p.span(start), - callee, - args: None, - type_args, - ..Default::default() - } - .into()); - } - - if p.input_mut().eat(&P::Token::SUPER) { - let base = Callee::Super(Super { - span: p.span(start), - }); - return parse_subscripts(p, base, true, false); - } else if p.input_mut().eat(&P::Token::IMPORT) { - return parse_dynamic_import_or_import_meta(p, start, true); - } - let obj = p.parse_primary_expr()?; - return_if_arrow!(p, obj); - - let type_args = if p.syntax().typescript() && p.input().is(&P::Token::LESS) { - try_parse_ts_type_args(p) - } else { - None - }; - let obj = if let Some(type_args) = type_args { - trace_cur!(p, parse_member_expr_or_new_expr__with_type_args); - TsInstantiation { - expr: obj, - type_args, - span: p.span(start), - } - .into() - } else { - obj - }; - - parse_subscripts(p, Callee::Expr(obj), true, false) -} - -/// Parse `NewExpression`. -/// This includes `MemberExpression`. -#[cfg_attr( - all(debug_assertions, feature = "tracing-spans"), - tracing::instrument(skip_all) -)] -pub fn parse_new_expr<'a>(p: &mut impl Parser<'a>) -> PResult> { - trace_cur!(p, parse_new_expr); - parse_member_expr_or_new_expr(p, true) -} - -/// Name from spec: 'LogicalORExpression' -pub fn parse_bin_expr<'a, P: Parser<'a>>(p: &mut P) -> PResult> { - trace_cur!(p, parse_bin_expr); - - let left = match p.parse_unary_expr() { - Ok(v) => v, - Err(err) => { - trace_cur!(p, parse_bin_expr__recovery_unary_err); - - let cur = p.input().cur(); - if cur.is_error() { - let err = p.input_mut().expect_error_token_and_bump(); - return Err(err); - } else if (cur.is_in() && p.ctx().contains(Context::IncludeInExpr)) - || cur.is_instanceof() - || cur.is_bin_op() - { - p.emit_err(p.input().cur_span(), SyntaxError::TS1109); - Invalid { span: err.span() }.into() - } else { - return Err(err); - } - } - }; - - return_if_arrow!(p, left); - parse_bin_op_recursively(p, left, 0) -} - -/// Parse binary operators with the operator precedence parsing -/// algorithm. `left` is the left-hand side of the operator. -/// `minPrec` provides context that allows the function to stop and -/// defer further parser to one of its callers when it encounters an -/// operator that has a lower precedence than the set it is parsing. -/// -/// `parseExprOp` -pub fn parse_bin_op_recursively<'a>( - p: &mut impl Parser<'a>, - mut left: Box, - mut min_prec: u8, -) -> PResult> { - loop { - let (next_left, next_prec) = parse_bin_op_recursively_inner(p, left, min_prec)?; - - match &*next_left { - Expr::Bin(BinExpr { - span, - left, - op: op!("&&"), - .. - }) - | Expr::Bin(BinExpr { - span, - left, - op: op!("||"), - .. - }) => { - if let Expr::Bin(BinExpr { op: op!("??"), .. }) = &**left { - p.emit_err(*span, SyntaxError::NullishCoalescingWithLogicalOp); - } - } - _ => {} - } - - min_prec = match next_prec { - Some(v) => v, - None => return Ok(next_left), - }; - - left = next_left; - } -} - -/// Returns `(left, Some(next_prec))` or `(expr, None)`. -fn parse_bin_op_recursively_inner<'a, P: Parser<'a>>( - p: &mut P, - left: Box, - min_prec: u8, -) -> PResult<(Box, Option)> { - const PREC_OF_IN: u8 = 7; - - if p.input().syntax().typescript() && !p.input().had_line_break_before_cur() { - if PREC_OF_IN > min_prec && p.input().is(&P::Token::AS) { - let start = left.span_lo(); - let expr = left; - let node = if peek!(p).is_some_and(|cur| cur.is_const()) { - p.bump(); // as - p.bump(); // const - TsConstAssertion { - span: p.span(start), - expr, - } - .into() - } else { - let type_ann = next_then_parse_ts_type(p)?; - TsAsExpr { - span: p.span(start), - expr, - type_ann, - } - .into() - }; - - return parse_bin_op_recursively_inner(p, node, min_prec); - } else if p.input().is(&P::Token::SATISFIES) { - let start = left.span_lo(); - let expr = left; - let node = { - let type_ann = next_then_parse_ts_type(p)?; - TsSatisfiesExpr { - span: p.span(start), - expr, - type_ann, - } - .into() - }; - - return parse_bin_op_recursively_inner(p, node, min_prec); - } - } - - // Return left on eof - let cur = p.input().cur(); - let op = if cur.is_in() && p.ctx().contains(Context::IncludeInExpr) { - op!("in") - } else if cur.is_instanceof() { - op!("instanceof") - } else if let Some(op) = cur.as_bin_op() { - op - } else { - return Ok((left, None)); - }; - - if op.precedence() <= min_prec { - if cfg!(feature = "debug") { - #[cfg(debug_assertions)] - tracing::trace!( - "returning {:?} without parsing {:?} because min_prec={}, prec={}", - left, - op, - min_prec, - op.precedence() - ); - } - - return Ok((left, None)); - } - p.bump(); - if cfg!(feature = "debug") { - #[cfg(debug_assertions)] - tracing::trace!( - "parsing binary op {:?} min_prec={}, prec={}", - op, - min_prec, - op.precedence() - ); - } - match *left { - // This is invalid syntax. - Expr::Unary { .. } | Expr::Await(..) if op == op!("**") => { - // Correct implementation would be returning Ok(left) and - // returning "unexpected token '**'" on next. - // But it's not useful error message. - - syntax_error!( - p, - SyntaxError::UnaryInExp { - // FIXME: Use display - left: format!("{left:?}"), - left_span: left.span(), - } - ) - } - _ => {} - } - - let right = { - let left_of_right = p.parse_unary_expr()?; - parse_bin_op_recursively( - p, - left_of_right, - if op == op!("**") { - // exponential operator is right associative - op.precedence() - 1 - } else { - op.precedence() - }, - )? - }; - /* this check is for all ?? operators - * a ?? b && c for this example - * b && c => This is considered as a logical expression in the ast tree - * a => Identifier - * so for ?? operator we need to check in this case the right expression to - * have parenthesis second case a && b ?? c - * here a && b => This is considered as a logical expression in the ast tree - * c => identifier - * so now here for ?? operator we need to check the left expression to have - * parenthesis if the parenthesis is missing we raise an error and - * throw it - */ - if op == op!("??") { - match *left { - Expr::Bin(BinExpr { span, op, .. }) if op == op!("&&") || op == op!("||") => { - p.emit_err(span, SyntaxError::NullishCoalescingWithLogicalOp); - } - _ => {} - } - - match *right { - Expr::Bin(BinExpr { span, op, .. }) if op == op!("&&") || op == op!("||") => { - p.emit_err(span, SyntaxError::NullishCoalescingWithLogicalOp); - } - _ => {} - } - } - - let node = BinExpr { - span: Span::new_with_checked(left.span_lo(), right.span_hi()), - op, - left, - right, - } - .into(); - - Ok((node, Some(min_prec))) -} - -/// Parse unary expression and update expression. -/// -/// spec: 'UnaryExpression' -pub(crate) fn parse_unary_expr<'a, P: Parser<'a>>(p: &mut P) -> PResult> { - trace_cur!(p, parse_unary_expr); - - let token_and_span = p.input().get_cur(); - let start = token_and_span.span().lo; - let cur = token_and_span.token(); - - if !p.input().syntax().jsx() && p.input().syntax().typescript() && cur.is_less() { - p.bump(); // consume `<` - if p.input_mut().eat(&P::Token::CONST) { - expect!(p, &P::Token::GREATER); - let expr = p.parse_unary_expr()?; - return Ok(TsConstAssertion { - span: p.span(start), - expr, - } - .into()); - } - - return parse_ts_type_assertion(p, start) - .map(Expr::from) - .map(Box::new); - } else if cur.is_plus_plus() || cur.is_minus_minus() { - // Parse update expression - let op = if cur.is_plus_plus() { - op!("++") - } else { - op!("--") - }; - p.bump(); - - let arg = p.parse_unary_expr()?; - let span = Span::new_with_checked(start, arg.span_hi()); - p.check_assign_target(&arg, false); - - return Ok(UpdateExpr { - span, - prefix: true, - op, - arg, - } - .into()); - } else if cur.is_delete() - || cur.is_void() - || cur.is_typeof() - || cur.is_plus() - || cur.is_minus() - || cur.is_tilde() - || cur.is_bang() - { - // Parse unary expression - let op = if cur.is_delete() { - op!("delete") - } else if cur.is_void() { - op!("void") - } else if cur.is_typeof() { - op!("typeof") - } else if cur.is_plus() { - op!(unary, "+") - } else if cur.is_minus() { - op!(unary, "-") - } else if cur.is_tilde() { - op!("~") - } else { - debug_assert!(cur.is_bang()); - op!("!") - }; - p.bump(); - let arg_start = p.cur_pos() - BytePos(1); - let arg = match p.parse_unary_expr() { - Ok(expr) => expr, - Err(err) => { - p.emit_error(err); - Invalid { - span: Span::new_with_checked(arg_start, arg_start), - } - .into() - } - }; - - if op == op!("delete") { - // Skip emitting TS1102 in TypeScript mode because it's a semantic error - // that should be handled by the type checker, not the parser. - // See: https://github.com/swc-project/swc/issues/10558 - if !p.input().syntax().typescript() { - if let Expr::Ident(ref i) = *arg { - p.emit_strict_mode_err(i.span, SyntaxError::TS1102) - } - } - } - - return Ok(UnaryExpr { - span: Span::new_with_checked(start, arg.span_hi()), - op, - arg, - } - .into()); - } else if cur.is_await() { - return parse_await_expr(p, None); - } - - // UpdateExpression - let expr = p.parse_lhs_expr()?; - return_if_arrow!(p, expr); - - // Line terminator isn't allowed here. - if p.input().had_line_break_before_cur() { - return Ok(expr); - } - - let cur = p.input().cur(); - if cur.is_plus_plus() || cur.is_minus_minus() { - let op = if cur.is_plus_plus() { - op!("++") - } else { - op!("--") - }; - p.check_assign_target(&expr, false); - p.bump(); - - return Ok(UpdateExpr { - span: p.span(expr.span_lo()), - prefix: false, - op, - arg: expr, - } - .into()); - } - Ok(expr) -} - -pub fn parse_await_expr<'a, P: Parser<'a>>( - p: &mut P, - start_of_await_token: Option, -) -> PResult> { - let start = start_of_await_token.unwrap_or_else(|| p.cur_pos()); - - if start_of_await_token.is_none() { - p.assert_and_bump(&P::Token::AWAIT); - } - - let await_token = p.span(start); - - if p.input().is(&P::Token::MUL) { - syntax_error!(p, SyntaxError::AwaitStar); - } - - let ctx = p.ctx(); - - let span = p.span(start); - - if !ctx.contains(Context::InAsync) - && (p.is_general_semi() || { - let cur = p.input().cur(); - cur.is_rparen() || cur.is_rbracket() || cur.is_comma() - }) - { - if ctx.contains(Context::Module) { - p.emit_err(span, SyntaxError::InvalidIdentInAsync); - } - - return Ok(Ident::new_no_ctxt(atom!("await"), span).into()); - } - - // This has been checked if start_of_await_token == true, - if start_of_await_token.is_none() && ctx.contains(Context::TopLevel) { - p.mark_found_module_item(); - if !ctx.contains(Context::CanBeModule) { - p.emit_err(await_token, SyntaxError::TopLevelAwaitInScript); - } - } - - if ctx.contains(Context::InFunction) && !ctx.contains(Context::InAsync) { - p.emit_err(await_token, SyntaxError::AwaitInFunction); - } - - if ctx.contains(Context::InParameters) && !ctx.contains(Context::InFunction) { - p.emit_err(span, SyntaxError::AwaitParamInAsync); - } - - let arg = p.parse_unary_expr()?; - Ok(AwaitExpr { - span: p.span(start), - arg, - } - .into()) -} - -pub(super) fn parse_for_head_prefix<'a>(p: &mut impl Parser<'a>) -> PResult> { - p.parse_expr() -} - -/// Parse call, dot, and `[]`-subscript expressions. -#[cfg_attr( - all(debug_assertions, feature = "tracing-spans"), - tracing::instrument(level = "debug", skip_all) -)] -pub fn parse_lhs_expr<'a, P: Parser<'a>, const PARSE_JSX: bool>(p: &mut P) -> PResult> { - trace_cur!(p, parse_lhs_expr); - - // parse jsx - if PARSE_JSX && p.input().syntax().jsx() { - fn into_expr(e: Either) -> Box { - match e { - Either::Left(l) => l.into(), - Either::Right(r) => r.into(), - } - } - let token_and_span = p.input().get_cur(); - let cur = token_and_span.token(); - if cur.is_jsx_text() { - return Ok(Box::new(Expr::Lit(Lit::JSXText(parse_jsx_text(p))))); - } else if cur.is_jsx_tag_start() { - return parse_jsx_element(p).map(into_expr); - } else if cur.is_error() { - let err = p.input_mut().expect_error_token_and_bump(); - return Err(err); - } - - if p.input().is(&P::Token::LESS) && !peek!(p).is_some_and(|peek| peek.is_bang()) { - // In case we encounter an lt token here it will always be the start of - // jsx as the lt sign is not allowed in places that expect an expression - - // FIXME: - // p.finishToken(tt.jsxTagStart); - - return parse_jsx_element(p).map(into_expr); - } - } - - let token_and_span = p.input().get_cur(); - let start = token_and_span.span().lo; - let cur = token_and_span.token(); - - // `super()` can't be handled from parse_new_expr() - if cur.is_super() { - p.bump(); // eat `super` - let obj = Callee::Super(Super { - span: p.span(start), - }); - return parse_subscripts(p, obj, false, false); - } else if cur.is_import() { - p.bump(); // eat `import` - return parse_dynamic_import_or_import_meta(p, start, false); - } - - let callee = parse_new_expr(p)?; - return_if_arrow!(p, callee); - - let type_args = if p.input().syntax().typescript() && { - let cur = p.input().cur(); - cur.is_less() || cur.is_lshift() - } { - try_parse_ts(p, |p| { - let type_args = parse_ts_type_args(p)?; - p.assert_and_bump(&P::Token::GREATER); - if p.input().is(&P::Token::LPAREN) { - Ok(Some(type_args)) - } else { - Ok(None) - } - }) - } else { - None - }; - - if let Expr::New(ne @ NewExpr { args: None, .. }) = *callee { - // If this is parsed using 'NewExpression' rule, just return it. - // Because it's not left-recursive. - if type_args.is_some() { - // This fails with `expected (` - expect!(p, &P::Token::LPAREN); - } - debug_assert!( - !p.input().cur().is_lparen(), - "parse_new_expr() should eat paren if it exists" - ); - return Ok(NewExpr { type_args, ..ne }.into()); - } - // 'CallExpr' rule contains 'MemberExpr (...)', - // and 'MemberExpr' rule contains 'new MemberExpr (...)' - - if p.input().is(&P::Token::LPAREN) { - // This is parsed using production MemberExpression, - // which is left-recursive. - let (callee, is_import) = match callee { - _ if callee.is_ident_ref_to("import") => ( - Callee::Import(Import { - span: callee.span(), - phase: Default::default(), - }), - true, - ), - _ => (Callee::Expr(callee), false), - }; - let args = parse_args(p, is_import)?; - - let call_expr = match callee { - Callee::Expr(e) if unwrap_ts_non_null(&e).is_opt_chain() => OptChainExpr { - span: p.span(start), - base: Box::new(OptChainBase::Call(OptCall { - span: p.span(start), - callee: e, - args, - type_args, - ..Default::default() - })), - optional: false, - } - .into(), - _ => CallExpr { - span: p.span(start), - - callee, - args, - type_args, - ..Default::default() - } - .into(), - }; - - return parse_subscripts(p, Callee::Expr(call_expr), false, false); - } - if type_args.is_some() { - // This fails - expect!(p, &P::Token::LPAREN); - } - - // This is parsed using production 'NewExpression', which contains - // 'MemberExpression' - Ok(callee) -} - -// Returns (args_or_pats, trailing_comma) -#[cfg_attr( - all(debug_assertions, feature = "tracing-spans"), - tracing::instrument(level = "debug", skip_all) -)] -pub fn parse_args_or_pats<'a, P: Parser<'a>>( - p: &mut P, -) -> PResult<(Vec, Option)> { - p.do_outside_of_context(Context::WillExpectColonForCond, parse_args_or_pats_inner) -} - -fn parse_args_or_pats_inner<'a, P: Parser<'a>>( - p: &mut P, -) -> PResult<(Vec, Option)> { - trace_cur!(p, parse_args_or_pats); - - expect!(p, &P::Token::LPAREN); - - let mut items = Vec::new(); - let mut trailing_comma = None; - - // TODO(kdy1): optimize (once we parsed a pattern, we can parse everything else - // as a pattern instead of reparsing) - while !p.input().is(&P::Token::RPAREN) { - // https://github.com/swc-project/swc/issues/410 - let is_async = p.input().is(&P::Token::ASYNC) - && peek!(p).is_some_and(|t| t.is_lparen() || t.is_word() || t.is_function()); - - let start = p.cur_pos(); - p.state_mut().potential_arrow_start = Some(start); - let modifier_start = start; - - let has_modifier = eat_any_ts_modifier(p)?; - let pat_start = p.cur_pos(); - - let mut arg = { - if p.input().syntax().typescript() - && (p.is_ident_ref() - || (p.input().is(&P::Token::DOTDOTDOT) && p.peek_is_ident_ref())) - { - let spread = if p.input_mut().eat(&P::Token::DOTDOTDOT) { - Some(p.input().prev_span()) - } else { - None - }; - - // At here, we use parse_bin_expr() instead of parse_assignment_expr() - // because `x?: number` should not be parsed as a conditional expression - let expr = if spread.is_some() { - parse_bin_expr(p)? - } else { - let mut expr = parse_bin_expr(p)?; - - if p.input().cur().is_assign_op() { - expr = finish_assignment_expr(p, start, expr)? - } - - expr - }; - - ExprOrSpread { spread, expr } - } else { - p.allow_in_expr(|p| p.parse_expr_or_spread())? - } - }; - - let optional = if p.input().syntax().typescript() { - if p.input().is(&P::Token::QUESTION) { - if peek!(p).is_some_and(|peek| { - peek.is_comma() || peek.is_equal() || peek.is_rparen() || peek.is_colon() - }) { - p.assert_and_bump(&P::Token::QUESTION); - if arg.spread.is_some() { - p.emit_err(p.input().prev_span(), SyntaxError::TS1047); - } - match *arg.expr { - Expr::Ident(..) => {} - _ => { - syntax_error!(p, arg.span(), SyntaxError::TsBindingPatCannotBeOptional) - } - } - true - } else if matches!(arg, ExprOrSpread { spread: None, .. }) { - expect!(p, &P::Token::QUESTION); - let test = arg.expr; - - let cons = p.do_inside_of_context( - Context::InCondExpr - .union(Context::WillExpectColonForCond) - .union(Context::IncludeInExpr), - parse_assignment_expr, - )?; - expect!(p, &P::Token::COLON); - - let alt = p.do_inside_of_context(Context::InCondExpr, |p| { - p.do_outside_of_context( - Context::WillExpectColonForCond, - parse_assignment_expr, - ) - })?; - - arg = ExprOrSpread { - spread: None, - expr: CondExpr { - span: Span::new_with_checked(start, alt.span_hi()), - test, - cons, - alt, - } - .into(), - }; - - false - } else { - false - } - } else { - false - } - } else { - false - }; - - if optional || (p.input().syntax().typescript() && p.input().is(&P::Token::COLON)) { - // TODO: `async(...args?: any[]) : any => {}` - // - // if p.input().syntax().typescript() && optional && arg.spread.is_some() { - // p.emit_err(p.input().prev_span(), SyntaxError::TS1047) - // } - - let mut pat = reparse_expr_as_pat(p, PatType::BindingPat, arg.expr)?; - if optional { - match pat { - Pat::Ident(ref mut i) => i.optional = true, - _ => unreachable!(), - } - } - if let Some(span) = arg.spread { - pat = RestPat { - span: p.span(pat_start), - dot3_token: span, - arg: Box::new(pat), - type_ann: None, - } - .into(); - } - match pat { - Pat::Ident(BindingIdent { - id: Ident { ref mut span, .. }, - ref mut type_ann, - .. - }) - | Pat::Array(ArrayPat { - ref mut type_ann, - ref mut span, - .. - }) - | Pat::Object(ObjectPat { - ref mut type_ann, - ref mut span, - .. - }) - | Pat::Rest(RestPat { - ref mut type_ann, - ref mut span, - .. - }) => { - let new_type_ann = try_parse_ts_type_ann(p)?; - if new_type_ann.is_some() { - *span = Span::new_with_checked(pat_start, p.input().prev_span().hi); - } - *type_ann = new_type_ann; - } - Pat::Expr(ref expr) => unreachable!("invalid pattern: Expr({:?})", expr), - Pat::Assign(..) | Pat::Invalid(..) => { - // We don't have to panic here. - // See: https://github.com/swc-project/swc/issues/1170 - // - // Also, as an exact error is added to the errors while - // creating `Invalid`, we don't have to emit a new - // error. - } - #[cfg(swc_ast_unknown)] - _ => (), - } - - if p.input_mut().eat(&P::Token::EQUAL) { - let right = parse_assignment_expr(p)?; - pat = AssignPat { - span: p.span(pat_start), - left: Box::new(pat), - right, - } - .into(); - } - - if has_modifier { - p.emit_err(p.span(modifier_start), SyntaxError::TS2369); - } - - items.push(AssignTargetOrSpread::Pat(pat)) - } else { - if has_modifier { - p.emit_err(p.span(modifier_start), SyntaxError::TS2369); - } - - items.push(AssignTargetOrSpread::ExprOrSpread(arg)); - } - - // https://github.com/swc-project/swc/issues/433 - if p.input_mut().eat(&P::Token::ARROW) && { - debug_assert_eq!(items.len(), 1); - match items[0] { - AssignTargetOrSpread::ExprOrSpread(ExprOrSpread { ref expr, .. }) - | AssignTargetOrSpread::Pat(Pat::Expr(ref expr)) => { - matches!(**expr, Expr::Ident(..)) - } - AssignTargetOrSpread::Pat(Pat::Ident(..)) => true, - _ => false, - } - } { - let params: Vec = parse_paren_items_as_params(p, items.clone(), None)?; - - let body: Box = parse_fn_block_or_expr_body( - p, - false, - false, - true, - params.is_simple_parameter_list(), - )?; - let span = p.span(start); - - items.push(AssignTargetOrSpread::ExprOrSpread(ExprOrSpread { - expr: Box::new( - ArrowExpr { - span, - body, - is_async, - is_generator: false, - params, - ..Default::default() - } - .into(), - ), - spread: None, - })); - } - - if !p.input().is(&P::Token::RPAREN) { - expect!(p, &P::Token::COMMA); - if p.input().is(&P::Token::RPAREN) { - trailing_comma = Some(p.input().prev_span()); - } - } - } - - expect!(p, &P::Token::RPAREN); - Ok((items, trailing_comma)) -} - -#[cfg_attr( - all(debug_assertions, feature = "tracing-spans"), - tracing::instrument(level = "debug", skip_all) -)] -pub fn parse_paren_expr_or_arrow_fn<'a, P: Parser<'a>>( - p: &mut P, - can_be_arrow: bool, - async_span: Option, -) -> PResult> { - trace_cur!(p, parse_paren_expr_or_arrow_fn); - - let expr_start = async_span.map(|x| x.lo()).unwrap_or_else(|| p.cur_pos()); - - // At this point, we can't know if it's parenthesized - // expression or head of arrow function. - // But as all patterns of javascript is subset of - // expressions, we can parse both as expression. - - let (paren_items, trailing_comma) = p - .do_outside_of_context(Context::WillExpectColonForCond, |p| { - p.allow_in_expr(parse_args_or_pats) - })?; - - let has_pattern = paren_items - .iter() - .any(|item| matches!(item, AssignTargetOrSpread::Pat(..))); - - let will_expect_colon_for_cond = p.ctx().contains(Context::WillExpectColonForCond); - // This is slow path. We handle arrow in conditional expression. - if p.syntax().typescript() - && p.ctx().contains(Context::InCondExpr) - && p.input().is(&P::Token::COLON) - { - // TODO: Remove clone - let items_ref = &paren_items; - if let Some(expr) = try_parse_ts(p, |p| { - let return_type = parse_ts_type_or_type_predicate_ann(p, &P::Token::COLON)?; - - expect!(p, &P::Token::ARROW); - - let params: Vec = - parse_paren_items_as_params(p, items_ref.clone(), trailing_comma)?; - - let body: Box = parse_fn_block_or_expr_body( - p, - async_span.is_some(), - false, - true, - params.is_simple_parameter_list(), - )?; - - if will_expect_colon_for_cond && !p.input().is(&P::Token::COLON) { - trace_cur!(p, parse_arrow_in_cond__fail); - unexpected!(p, "fail") - } - - Ok(Some( - ArrowExpr { - span: p.span(expr_start), - is_async: async_span.is_some(), - is_generator: false, - params, - body, - return_type: Some(return_type), - ..Default::default() - } - .into(), - )) - }) { - return Ok(expr); - } - } - - let return_type = if !p.ctx().contains(Context::WillExpectColonForCond) - && p.input().syntax().typescript() - && p.input().is(&P::Token::COLON) - { - try_parse_ts(p, |p| { - let return_type = parse_ts_type_or_type_predicate_ann(p, &P::Token::COLON)?; - - if !p.input().is(&P::Token::ARROW) { - unexpected!(p, "fail") - } - - Ok(Some(return_type)) - }) - } else { - None - }; - - // we parse arrow function at here, to handle it efficiently. - if has_pattern || return_type.is_some() || p.input().is(&P::Token::ARROW) { - if p.input().had_line_break_before_cur() { - syntax_error!(p, p.span(expr_start), SyntaxError::LineBreakBeforeArrow); - } - - if !can_be_arrow { - syntax_error!(p, p.span(expr_start), SyntaxError::ArrowNotAllowed); - } - expect!(p, &P::Token::ARROW); - - let params: Vec = parse_paren_items_as_params(p, paren_items, trailing_comma)?; - - let body: Box = parse_fn_block_or_expr_body( - p, - async_span.is_some(), - false, - true, - params.is_simple_parameter_list(), - )?; - let arrow_expr = ArrowExpr { - span: p.span(expr_start), - is_async: async_span.is_some(), - is_generator: false, - params, - body, - return_type, - ..Default::default() - }; - if let BlockStmtOrExpr::BlockStmt(..) = &*arrow_expr.body { - if p.input().cur().is_bin_op() { - // ) is required - p.emit_err(p.input().cur_span(), SyntaxError::TS1005); - let errorred_expr = parse_bin_op_recursively(p, Box::new(arrow_expr.into()), 0)?; - - if !p.is_general_semi() { - // ; is required - p.emit_err(p.input().cur_span(), SyntaxError::TS1005); - } - - return Ok(errorred_expr); - } - } - return Ok(arrow_expr.into()); - } else { - // If there's no arrow function, we have to check there's no - // AssignProp in lhs to check against assignment in object literals - // like (a, {b = 1}); - for expr_or_spread in paren_items.iter() { - if let AssignTargetOrSpread::ExprOrSpread(e) = expr_or_spread { - if let Expr::Object(o) = &*e.expr { - for prop in o.props.iter() { - if let PropOrSpread::Prop(prop) = prop { - if let Prop::Assign(..) = **prop { - p.emit_err(prop.span(), SyntaxError::AssignProperty); - } - } - } - } - } - } - } - - let expr_or_spreads = paren_items - .into_iter() - .map(|item| -> PResult<_> { - match item { - AssignTargetOrSpread::ExprOrSpread(e) => Ok(e), - _ => syntax_error!(p, item.span(), SyntaxError::InvalidExpr), - } - }) - .collect::, _>>()?; - if let Some(async_span) = async_span { - // It's a call expression - return Ok(CallExpr { - span: p.span(async_span.lo()), - callee: Callee::Expr(Box::new( - Ident::new_no_ctxt(atom!("async"), async_span).into(), - )), - args: expr_or_spreads, - ..Default::default() - } - .into()); - } - - // It was not head of arrow function. - - if expr_or_spreads.is_empty() { - syntax_error!( - p, - Span::new_with_checked(expr_start, p.last_pos()), - SyntaxError::EmptyParenExpr - ); - } - - // TODO: Verify that invalid expression like {a = 1} does not exists. - - // ParenthesizedExpression cannot contain spread. - if expr_or_spreads.len() == 1 { - let expr = match expr_or_spreads.into_iter().next().unwrap() { - ExprOrSpread { - spread: Some(..), - ref expr, - } => syntax_error!(p, expr.span(), SyntaxError::SpreadInParenExpr), - ExprOrSpread { expr, .. } => expr, - }; - Ok(ParenExpr { - span: p.span(expr_start), - expr, - } - .into()) - } else { - debug_assert!(expr_or_spreads.len() >= 2); - - let mut exprs = Vec::with_capacity(expr_or_spreads.len()); - for expr in expr_or_spreads { - match expr { - ExprOrSpread { - spread: Some(..), - ref expr, - } => syntax_error!(p, expr.span(), SyntaxError::SpreadInParenExpr), - ExprOrSpread { expr, .. } => exprs.push(expr), - } - } - debug_assert!(exprs.len() >= 2); - - // span of sequence expression should not include '(', ')' - let seq_expr = SeqExpr { - span: Span::new_with_checked( - exprs.first().unwrap().span_lo(), - exprs.last().unwrap().span_hi(), - ), - exprs, - } - .into(); - Ok(ParenExpr { - span: p.span(expr_start), - expr: seq_expr, - } - .into()) - } -} - -pub fn parse_primary_expr_rest<'a, P: Parser<'a>>( - p: &mut P, - start: BytePos, - can_be_arrow: bool, -) -> PResult> { - let decorators = if p.input().is(&P::Token::AT) { - Some(parse_decorators(p, false)?) - } else { - None - }; - - let token_and_span = p.input().get_cur(); - let cur = token_and_span.token(); - - if cur.is_class() { - return parse_class_expr(p, start, decorators.unwrap_or_default()); - } - - let try_parse_arrow_expr = |p: &mut P, id: Ident, id_is_async| -> PResult> { - if can_be_arrow && !p.input().had_line_break_before_cur() { - if id_is_async && p.is_ident_ref() { - // see https://github.com/tc39/ecma262/issues/2034 - // ```js - // for(async of - // for(async of x); - // for(async of =>{};;); - // ``` - let ctx = p.ctx(); - if ctx.contains(Context::ForLoopInit) - && p.input().is(&P::Token::OF) - && !peek!(p).is_some_and(|peek| peek.is_arrow()) - { - // ```spec https://tc39.es/ecma262/#prod-ForInOfStatement - // for ( [lookahead ∉ { let, async of }] LeftHandSideExpression[?Yield, ?Await] of AssignmentExpression[+In, ?Yield, ?Await] ) Statement[?Yield, ?Await, ?Return] - // [+Await] for await ( [lookahead ≠ let] LeftHandSideExpression[?Yield, ?Await] of AssignmentExpression[+In, ?Yield, ?Await] ) Statement[?Yield, ?Await, ?Return] - // ``` - - if !ctx.contains(Context::ForAwaitLoopInit) { - p.emit_err(p.input().prev_span(), SyntaxError::TS1106); - } - - return Ok(id.into()); - } - - let ident = parse_binding_ident(p, false)?; - if p.input().syntax().typescript() - && ident.sym == "as" - && !p.input().is(&P::Token::ARROW) - { - // async as type - let type_ann = p.in_type(parse_ts_type)?; - return Ok(TsAsExpr { - span: p.span(start), - expr: Box::new(id.into()), - type_ann, - } - .into()); - } - - // async a => body - let arg = ident.into(); - let params = vec![arg]; - expect!(p, &P::Token::ARROW); - let body = parse_fn_block_or_expr_body( - p, - true, - false, - true, - params.is_simple_parameter_list(), - )?; - - return Ok(ArrowExpr { - span: p.span(start), - body, - params, - is_async: true, - is_generator: false, - ..Default::default() - } - .into()); - } else if p.input_mut().eat(&P::Token::ARROW) { - if p.ctx().contains(Context::Strict) && id.is_reserved_in_strict_bind() { - p.emit_strict_mode_err(id.span, SyntaxError::EvalAndArgumentsInStrict) - } - let params = vec![id.into()]; - let body = parse_fn_block_or_expr_body( - p, - false, - false, - true, - params.is_simple_parameter_list(), - )?; - - return Ok(ArrowExpr { - span: p.span(start), - body, - params, - is_async: false, - is_generator: false, - ..Default::default() - } - .into()); - } - } - - Ok(id.into()) - }; - - let token_start = token_and_span.span().lo; - if cur.is_let() || (p.input().syntax().typescript() && cur.is_await()) { - let ctx = p.ctx(); - let id = parse_ident( - p, - !ctx.contains(Context::InGenerator), - !ctx.contains(Context::InAsync), - )?; - try_parse_arrow_expr(p, id, false) - } else if cur.is_hash() { - p.bump(); // consume `#` - let id = parse_ident_name(p)?; - Ok(PrivateName { - span: p.span(start), - name: id.sym, - } - .into()) - } else if cur.is_unknown_ident() { - let word = p.input_mut().expect_word_token_and_bump(); - if p.ctx().contains(Context::InClassField) && word == atom!("arguments") { - p.emit_err(p.input().prev_span(), SyntaxError::ArgumentsInClassField) - }; - let id = Ident::new_no_ctxt(word, p.span(token_start)); - try_parse_arrow_expr(p, id, false) - } else if p.is_ident_ref() { - let id_is_async = p.input().cur().is_async(); - let word = p.input_mut().expect_word_token_and_bump(); - let id = Ident::new_no_ctxt(word, p.span(token_start)); - try_parse_arrow_expr(p, id, id_is_async) - } else { - syntax_error!(p, p.input().cur_span(), SyntaxError::TS1109) - } -} - -pub fn try_parse_regexp<'a, P: Parser<'a>>(p: &mut P, start: BytePos) -> Option> { - // Regexp - debug_assert!(p.input().cur().is_slash() || p.input().cur().is_slash_eq()); - - p.input_mut().set_next_regexp(Some(start)); - - p.bump(); // `/` or `/=` - - let cur = p.input().cur(); - if cur.is_regexp() { - p.input_mut().set_next_regexp(None); - let (exp, flags) = p.input_mut().expect_regex_token_and_bump(); - let span = p.span(start); - - let mut flags_count = - flags - .chars() - .fold(FxHashMap::::default(), |mut map, flag| { - let key = match flag { - // https://tc39.es/ecma262/#sec-isvalidregularexpressionliteral - 'd' | 'g' | 'i' | 'm' | 's' | 'u' | 'v' | 'y' => flag, - _ => '\u{0000}', // special marker for unknown flags - }; - map.entry(key).and_modify(|count| *count += 1).or_insert(1); - map - }); - - if flags_count.remove(&'\u{0000}').is_some() { - p.emit_err(span, SyntaxError::UnknownRegExpFlags); - } - - if let Some((flag, _)) = flags_count.iter().find(|(_, count)| **count > 1) { - p.emit_err(span, SyntaxError::DuplicatedRegExpFlags(*flag)); - } - - Some(Lit::Regex(Regex { span, exp, flags }).into()) - } else { - None - } -} - -pub fn try_parse_async_start<'a, P: Parser<'a>>( - p: &mut P, - can_be_arrow: bool, -) -> Option>> { - if peek!(p).is_some_and(|peek| peek.is_function()) - && !p.input_mut().has_linebreak_between_cur_and_peeked() - { - // handle `async function` expression - return Some(parse_async_fn_expr(p)); - } - - if can_be_arrow - && p.input().syntax().typescript() - && peek!(p).is_some_and(|peek| peek.is_less()) - { - // try parsing `async() => {}` - if let Some(res) = try_parse_ts(p, |p| { - let start = p.cur_pos(); - p.assert_and_bump(&P::Token::ASYNC); - try_parse_ts_generic_async_arrow_fn(p, start) - }) { - return Some(Ok(res.into())); - } - } - - if can_be_arrow - && peek!(p).is_some_and(|peek| peek.is_lparen()) - && !p.input_mut().has_linebreak_between_cur_and_peeked() - { - if let Err(e) = p.expect(&P::Token::ASYNC) { - return Some(Err(e)); - } - let async_span = p.input().prev_span(); - return Some(parse_paren_expr_or_arrow_fn( - p, - can_be_arrow, - Some(async_span), - )); - } - - None -} - -pub fn parse_this_expr<'a>(p: &mut impl Parser<'a>, start: BytePos) -> PResult> { - debug_assert!(p.input().cur().is_this()); - p.input_mut().bump(); - Ok(ThisExpr { - span: p.span(start), - } - .into()) -} - -/// Parse a primary expression or arrow function -#[cfg_attr( - all(debug_assertions, feature = "tracing-spans"), - tracing::instrument(level = "debug", skip_all) -)] -pub(crate) fn parse_primary_expr<'a, P: Parser<'a>>(p: &mut P) -> PResult> { - trace_cur!(p, parse_primary_expr); - - let start = p.cur_pos(); - - let can_be_arrow = p - .state_mut() - .potential_arrow_start - .map(|s| s == start) - .unwrap_or(false); - - let token = p.input().cur(); - if token.is_this() { - return parse_this_expr(p, start); - } else if token.is_async() { - if let Some(res) = try_parse_async_start(p, can_be_arrow) { - return res; - } - } else if token.is_lbracket() { - return p.do_outside_of_context(Context::WillExpectColonForCond, parse_array_lit); - } else if token.is_lbrace() { - return parse_object_expr(p).map(Box::new); - } else if token.is_function() { - return parse_fn_expr(p); - } else if token.is_null() - || token.is_true() - || token.is_false() - || token.is_num() - || token.is_bigint() - || token.is_str() - { - // Literals - return Ok(parse_lit(p)?.into()); - } else if token.is_slash() || token.is_slash_eq() { - if let Some(res) = try_parse_regexp(p, start) { - return Ok(res); - } - } else if token.is_lparen() { - return parse_paren_expr_or_arrow_fn(p, can_be_arrow, None); - } else if token.is_backquote() { - // parse template literal - return Ok((p - .do_outside_of_context(Context::WillExpectColonForCond, |p| parse_tpl(p, false)))? - .into()); - } - - parse_primary_expr_rest(p, start, can_be_arrow) -} diff --git a/crates/swc_ecma_lexer/src/common/parser/expr_ext.rs b/crates/swc_ecma_lexer/src/common/parser/expr_ext.rs deleted file mode 100644 index aa5e2bd14756..000000000000 --- a/crates/swc_ecma_lexer/src/common/parser/expr_ext.rs +++ /dev/null @@ -1,95 +0,0 @@ -use swc_ecma_ast::{ - EsReserved, Expr, MemberExpr, ParenExpr, TsAsExpr, TsInstantiation, TsNonNullExpr, - TsSatisfiesExpr, TsTypeAssertion, -}; - -pub trait ExprExt { - fn as_expr(&self) -> &Expr; - - /// "IsValidSimpleAssignmentTarget" from spec. - fn is_valid_simple_assignment_target(&self, strict: bool) -> bool { - match self.as_expr() { - Expr::Ident(ident) => { - if strict && ident.is_reserved_in_strict_bind() { - return false; - } - true - } - - Expr::This(..) - | Expr::Lit(..) - | Expr::Array(..) - | Expr::Object(..) - | Expr::Fn(..) - | Expr::Class(..) - | Expr::Tpl(..) - | Expr::TaggedTpl(..) => false, - Expr::Paren(ParenExpr { expr, .. }) => expr.is_valid_simple_assignment_target(strict), - - Expr::Member(MemberExpr { obj, .. }) => match obj.as_ref() { - Expr::Member(..) => obj.is_valid_simple_assignment_target(strict), - Expr::OptChain(..) => false, - _ => true, - }, - - Expr::SuperProp(..) => true, - - Expr::New(..) | Expr::Call(..) => false, - // TODO: Spec only mentions `new.target` - Expr::MetaProp(..) => false, - - Expr::Update(..) => false, - - Expr::Unary(..) | Expr::Await(..) => false, - - Expr::Bin(..) => false, - - Expr::Cond(..) => false, - - Expr::Yield(..) | Expr::Arrow(..) | Expr::Assign(..) => false, - - Expr::Seq(..) => false, - - Expr::OptChain(..) => false, - - // MemberExpression is valid assignment target - Expr::PrivateName(..) => false, - - // jsx - Expr::JSXMember(..) - | Expr::JSXNamespacedName(..) - | Expr::JSXEmpty(..) - | Expr::JSXElement(..) - | Expr::JSXFragment(..) => false, - - // typescript - Expr::TsNonNull(TsNonNullExpr { ref expr, .. }) - | Expr::TsTypeAssertion(TsTypeAssertion { ref expr, .. }) - | Expr::TsAs(TsAsExpr { ref expr, .. }) - | Expr::TsInstantiation(TsInstantiation { ref expr, .. }) - | Expr::TsSatisfies(TsSatisfiesExpr { ref expr, .. }) => { - expr.is_valid_simple_assignment_target(strict) - } - - Expr::TsConstAssertion(..) => false, - - Expr::Invalid(..) => false, - - #[cfg(swc_ast_unknown)] - _ => false, - } - } -} - -impl ExprExt for Box { - #[inline(always)] - fn as_expr(&self) -> &Expr { - self - } -} -impl ExprExt for Expr { - #[inline(always)] - fn as_expr(&self) -> &Expr { - self - } -} diff --git a/crates/swc_ecma_lexer/src/common/parser/ident.rs b/crates/swc_ecma_lexer/src/common/parser/ident.rs deleted file mode 100644 index 61f4b60e0c55..000000000000 --- a/crates/swc_ecma_lexer/src/common/parser/ident.rs +++ /dev/null @@ -1,217 +0,0 @@ -use either::Either; -use swc_atoms::atom; -use swc_common::BytePos; -use swc_ecma_ast::*; - -use super::{buffer::Buffer, expr::parse_str_lit, PResult, Parser}; -use crate::{ - common::{context::Context, lexer::token::TokenFactory, parser::token_and_span::TokenAndSpan}, - error::SyntaxError, -}; - -// https://tc39.es/ecma262/#prod-ModuleExportName -pub fn parse_module_export_name<'a, P: Parser<'a>>(p: &mut P) -> PResult { - let cur = p.input().cur(); - let module_export_name = if cur.is_str() { - ModuleExportName::Str(parse_str_lit(p)) - } else if cur.is_word() { - ModuleExportName::Ident(parse_ident_name(p)?.into()) - } else { - unexpected!(p, "identifier or string"); - }; - Ok(module_export_name) -} - -/// Use this when spec says "IdentifierName". -/// This allows idents like `catch`. -pub fn parse_ident_name<'a, P: Parser<'a>>(p: &mut P) -> PResult { - let token_and_span = p.input().get_cur(); - let start = token_and_span.span().lo; - let cur = token_and_span.token(); - let w = if cur.is_word() { - p.input_mut().expect_word_token_and_bump() - } else if cur.is_jsx_name() && p.ctx().contains(Context::InType) { - p.input_mut().expect_jsx_name_token_and_bump() - } else { - syntax_error!(p, SyntaxError::ExpectedIdent) - }; - Ok(IdentName::new(w, p.span(start))) -} - -pub fn parse_maybe_private_name<'a, P: Parser<'a>>( - p: &mut P, -) -> PResult> { - let is_private = p.input().is(&P::Token::HASH); - if is_private { - parse_private_name(p).map(Either::Left) - } else { - parse_ident_name(p).map(Either::Right) - } -} - -pub fn parse_private_name<'a, P: Parser<'a>>(p: &mut P) -> PResult { - let start = p.cur_pos(); - p.assert_and_bump(&P::Token::HASH); - let hash_end = p.input().prev_span().hi; - if p.input().cur_pos() - hash_end != BytePos(0) { - syntax_error!(p, p.span(start), SyntaxError::SpaceBetweenHashAndIdent); - } - let id = parse_ident_name(p)?; - Ok(PrivateName { - span: p.span(start), - name: id.sym, - }) -} - -/// IdentifierReference -#[inline] -pub fn parse_ident_ref<'a>(p: &mut impl Parser<'a>) -> PResult { - let ctx = p.ctx(); - parse_ident( - p, - !ctx.contains(Context::InGenerator), - !ctx.contains(Context::InAsync), - ) -} - -/// LabelIdentifier -#[inline] -pub fn parse_label_ident<'a>(p: &mut impl Parser<'a>) -> PResult { - parse_ident_ref(p) -} - -/// babel: `parseBindingIdentifier` -/// -/// spec: `BindingIdentifier` -pub fn parse_binding_ident<'a>( - p: &mut impl Parser<'a>, - disallow_let: bool, -) -> PResult { - trace_cur!(p, parse_binding_ident); - - let cur = p.input().cur(); - if disallow_let && cur.is_let() { - unexpected!(p, "let is reserved in const, let, class declaration") - } else if cur.is_unknown_ident() { - let span = p.input().cur_span(); - let word = p.input_mut().expect_word_token_and_bump(); - if atom!("arguments") == word || atom!("eval") == word { - p.emit_strict_mode_err(span, SyntaxError::EvalAndArgumentsInStrict); - } - return Ok(Ident::new_no_ctxt(word, span).into()); - } - - // "yield" and "await" is **lexically** accepted. - let ident = parse_ident(p, true, true)?; - let ctx = p.ctx(); - if (ctx.intersects(Context::InAsync.union(Context::InStaticBlock)) && ident.sym == "await") - || (ctx.contains(Context::InGenerator) && ident.sym == "yield") - { - p.emit_err(ident.span, SyntaxError::ExpectedIdent); - } - - Ok(ident.into()) -} - -pub fn parse_opt_binding_ident<'a>( - p: &mut impl Parser<'a>, - disallow_let: bool, -) -> PResult> { - trace_cur!(p, parse_opt_binding_ident); - let token_and_span = p.input().get_cur(); - let cur = token_and_span.token(); - if cur.is_this() && p.input().syntax().typescript() { - let start = token_and_span.span().lo; - Ok(Some( - Ident::new_no_ctxt(atom!("this"), p.span(start)).into(), - )) - } else if cur.is_word() && !cur.is_reserved(p.ctx()) { - parse_binding_ident(p, disallow_let).map(Some) - } else { - Ok(None) - } -} - -/// Identifier -/// -/// In strict mode, "yield" is SyntaxError if matched. -pub fn parse_ident<'a>( - p: &mut impl Parser<'a>, - incl_yield: bool, - incl_await: bool, -) -> PResult { - trace_cur!(p, parse_ident); - - let token_and_span = p.input().get_cur(); - if !token_and_span.token().is_word() { - syntax_error!(p, SyntaxError::ExpectedIdent) - } - let span = token_and_span.span(); - let start = span.lo; - let t = token_and_span.token(); - - // Spec: - // It is a Syntax Error if this phrase is contained in strict mode code and the - // StringValue of IdentifierName is: "implements", "interface", "let", - // "package", "private", "protected", "public", "static", or "yield". - if t.is_enum() { - let word = p.input_mut().expect_word_token_and_bump(); - p.emit_err(span, SyntaxError::InvalidIdentInStrict(word.clone())); - return Ok(Ident::new_no_ctxt(word, p.span(start))); - } else if t.is_yield() - || t.is_let() - || t.is_static() - || t.is_implements() - || t.is_interface() - || t.is_package() - || t.is_private() - || t.is_protected() - || t.is_public() - { - let word = p.input_mut().expect_word_token_and_bump(); - p.emit_strict_mode_err(span, SyntaxError::InvalidIdentInStrict(word.clone())); - return Ok(Ident::new_no_ctxt(word, p.span(start))); - }; - - let word; - - // Spec: - // It is a Syntax Error if StringValue of IdentifierName is the same String - // value as the StringValue of any ReservedWord except for yield or await. - if t.is_await() { - let ctx = p.ctx(); - if ctx.contains(Context::InDeclare) { - word = atom!("await"); - } else if ctx.contains(Context::InStaticBlock) { - syntax_error!(p, span, SyntaxError::ExpectedIdent) - } else if ctx.contains(Context::Module) | ctx.contains(Context::InAsync) { - syntax_error!(p, span, SyntaxError::InvalidIdentInAsync) - } else if incl_await { - word = atom!("await") - } else { - syntax_error!(p, span, SyntaxError::ExpectedIdent) - } - } else if t.is_this() && p.input().syntax().typescript() { - word = atom!("this") - } else if t.is_let() { - word = atom!("let") - } else if t.is_known_ident() { - let ident = t.take_known_ident(); - word = ident - } else if t.is_unknown_ident() { - let word = p.input_mut().expect_word_token_and_bump(); - if p.ctx().contains(Context::InClassField) && word == atom!("arguments") { - p.emit_err(span, SyntaxError::ArgumentsInClassField) - } - return Ok(Ident::new_no_ctxt(word, p.span(start))); - } else if t.is_yield() && incl_yield { - word = atom!("yield") - } else if t.is_null() || t.is_true() || t.is_false() || t.is_keyword() { - syntax_error!(p, span, SyntaxError::ExpectedIdent) - } else { - unreachable!() - } - p.bump(); - - Ok(Ident::new_no_ctxt(word, p.span(start))) -} diff --git a/crates/swc_ecma_lexer/src/common/parser/is_directive.rs b/crates/swc_ecma_lexer/src/common/parser/is_directive.rs deleted file mode 100644 index 3408b3c2266d..000000000000 --- a/crates/swc_ecma_lexer/src/common/parser/is_directive.rs +++ /dev/null @@ -1,32 +0,0 @@ -use swc_ecma_ast::{ModuleItem, Stmt}; - -pub trait IsDirective { - fn as_ref(&self) -> Option<&Stmt>; - fn is_use_strict(&self) -> bool { - self.as_ref().is_some_and(Stmt::is_use_strict) - } -} - -impl IsDirective for Box -where - T: IsDirective, -{ - fn as_ref(&self) -> Option<&Stmt> { - T::as_ref(&**self) - } -} - -impl IsDirective for Stmt { - fn as_ref(&self) -> Option<&Stmt> { - Some(self) - } -} - -impl IsDirective for ModuleItem { - fn as_ref(&self) -> Option<&Stmt> { - match *self { - ModuleItem::Stmt(ref s) => Some(s), - _ => None, - } - } -} diff --git a/crates/swc_ecma_lexer/src/common/parser/is_invalid_class_name.rs b/crates/swc_ecma_lexer/src/common/parser/is_invalid_class_name.rs deleted file mode 100644 index fd0ef0d0eca3..000000000000 --- a/crates/swc_ecma_lexer/src/common/parser/is_invalid_class_name.rs +++ /dev/null @@ -1,21 +0,0 @@ -use swc_common::Span; -use swc_ecma_ast::Ident; - -pub trait IsInvalidClassName { - fn invalid_class_name(&self) -> Option; -} - -impl IsInvalidClassName for Ident { - fn invalid_class_name(&self) -> Option { - match &*self.sym { - "string" | "null" | "number" | "object" | "any" | "unknown" | "boolean" | "bigint" - | "symbol" | "void" | "never" | "intrinsic" => Some(self.span), - _ => None, - } - } -} -impl IsInvalidClassName for Option { - fn invalid_class_name(&self) -> Option { - self.as_ref().and_then(|i| i.invalid_class_name()) - } -} diff --git a/crates/swc_ecma_lexer/src/common/parser/is_simple_param_list.rs b/crates/swc_ecma_lexer/src/common/parser/is_simple_param_list.rs deleted file mode 100644 index f3c380b6e04b..000000000000 --- a/crates/swc_ecma_lexer/src/common/parser/is_simple_param_list.rs +++ /dev/null @@ -1,32 +0,0 @@ -use swc_ecma_ast::{Param, ParamOrTsParamProp, Pat}; - -pub trait IsSimpleParameterList { - fn is_simple_parameter_list(&self) -> bool; -} - -impl IsSimpleParameterList for Vec { - fn is_simple_parameter_list(&self) -> bool { - self.iter().all(|param| matches!(param.pat, Pat::Ident(_))) - } -} - -impl IsSimpleParameterList for Vec { - fn is_simple_parameter_list(&self) -> bool { - self.iter().all(|pat| matches!(pat, Pat::Ident(_))) - } -} - -impl IsSimpleParameterList for Vec { - fn is_simple_parameter_list(&self) -> bool { - self.iter().all(|param| { - matches!( - param, - ParamOrTsParamProp::TsParamProp(..) - | ParamOrTsParamProp::Param(Param { - pat: Pat::Ident(_), - .. - }) - ) - }) - } -} diff --git a/crates/swc_ecma_lexer/src/common/parser/jsx.rs b/crates/swc_ecma_lexer/src/common/parser/jsx.rs deleted file mode 100644 index 203f75f6d115..000000000000 --- a/crates/swc_ecma_lexer/src/common/parser/jsx.rs +++ /dev/null @@ -1,461 +0,0 @@ -use either::Either; -use swc_common::{BytePos, Span, Spanned}; -use swc_ecma_ast::*; - -use super::{PResult, Parser}; -use crate::{ - common::{ - context::Context, - lexer::token::TokenFactory, - parser::{ - buffer::Buffer, - eof_error, - expr::{parse_assignment_expr, parse_str_lit}, - get_qualified_jsx_name, - ident::parse_ident_ref, - typescript::{parse_ts_type_args, try_parse_ts}, - }, - }, - error::SyntaxError, -}; - -/// Parses JSX closing tag starting after ">( - p: &mut P, - start: BytePos, -) -> PResult> { - debug_assert!(p.input().syntax().jsx()); - - if p.input_mut().eat(&P::Token::JSX_TAG_END) { - return Ok(Either::Left(JSXClosingFragment { - span: p.span(start), - })); - } - - let name = parse_jsx_element_name(p)?; - expect!(p, &P::Token::JSX_TAG_END); - Ok(Either::Right(JSXClosingElement { - span: p.span(start), - name, - })) -} - -/// Parses JSX expression enclosed into curly brackets. -pub fn parse_jsx_expr_container<'a, P: Parser<'a>>(p: &mut P) -> PResult { - debug_assert!(p.input().syntax().jsx()); - debug_assert!(p.input().is(&P::Token::LBRACE)); - - let start = p.input().cur_pos(); - p.bump(); // bump "{" - let expr = if p.input().is(&P::Token::RBRACE) { - JSXExpr::JSXEmptyExpr(parse_jsx_empty_expr(p)) - } else { - p.parse_expr().map(JSXExpr::Expr)? - }; - expect!(p, &P::Token::RBRACE); - Ok(JSXExprContainer { - span: p.span(start), - expr, - }) -} - -/// Parse next token as JSX identifier -fn parse_jsx_ident<'a, P: Parser<'a>>(p: &mut P) -> PResult { - debug_assert!(p.input().syntax().jsx()); - trace_cur!(p, parse_jsx_ident); - let cur = p.input().cur(); - if cur.is_jsx_name() { - let name = p.input_mut().expect_jsx_name_token_and_bump(); - let span = p.input().prev_span(); - Ok(Ident::new_no_ctxt(name, span)) - } else if p.ctx().contains(Context::InForcedJsxContext) { - parse_ident_ref(p) - } else { - unexpected!(p, "jsx identifier") - } -} - -/// Parse namespaced identifier. -fn parse_jsx_namespaced_name<'a, P: Parser<'a>>(p: &mut P) -> PResult { - debug_assert!(p.input().syntax().jsx()); - trace_cur!(p, parse_jsx_namespaced_name); - let start = p.input().cur_pos(); - let ns = parse_jsx_ident(p)?.into(); - if !p.input_mut().eat(&P::Token::COLON) { - return Ok(JSXAttrName::Ident(ns)); - } - let name = parse_jsx_ident(p).map(IdentName::from)?; - Ok(JSXAttrName::JSXNamespacedName(JSXNamespacedName { - span: Span::new_with_checked(start, name.span.hi), - ns, - name, - })) -} - -/// Parses element name in any form - namespaced, member or single -/// identifier. -fn parse_jsx_element_name<'a, P: Parser<'a>>(p: &mut P) -> PResult { - debug_assert!(p.input().syntax().jsx()); - trace_cur!(p, parse_jsx_element_name); - let start = p.input().cur_pos(); - let mut node = match parse_jsx_namespaced_name(p)? { - JSXAttrName::Ident(i) => JSXElementName::Ident(i.into()), - JSXAttrName::JSXNamespacedName(i) => JSXElementName::JSXNamespacedName(i), - #[cfg(swc_ast_unknown)] - _ => unreachable!(), - }; - while p.input_mut().eat(&P::Token::DOT) { - let prop = parse_jsx_ident(p).map(IdentName::from)?; - let new_node = JSXElementName::JSXMemberExpr(JSXMemberExpr { - span: p.span(start), - obj: match node { - JSXElementName::Ident(i) => JSXObject::Ident(i), - JSXElementName::JSXMemberExpr(i) => JSXObject::JSXMemberExpr(Box::new(i)), - _ => unimplemented!("JSXNamespacedName -> JSXObject"), - }, - prop, - }); - node = new_node; - } - Ok(node) -} - -/// JSXEmptyExpression is unique type since it doesn't actually parse -/// anything, and so it should start at the end of last read token (left -/// brace) and finish at the beginning of the next one (right brace). -pub fn parse_jsx_empty_expr<'a>(p: &mut impl Parser<'a>) -> JSXEmptyExpr { - debug_assert!(p.input().syntax().jsx()); - let start = p.input().cur_pos(); - JSXEmptyExpr { - span: Span::new_with_checked(start, start), - } -} - -pub fn parse_jsx_text<'a>(p: &mut impl Parser<'a>) -> JSXText { - debug_assert!(p.input().syntax().jsx()); - - let cur = p.input().cur(); - debug_assert!(cur.is_jsx_text()); - let (value, raw) = p.input_mut().expect_jsx_text_token_and_bump(); - let span = p.input().prev_span(); - JSXText { span, value, raw } -} - -pub fn jsx_expr_container_to_jsx_attr_value<'a, P: Parser<'a>>( - p: &mut P, - start: BytePos, - node: JSXExprContainer, -) -> PResult { - match node.expr { - JSXExpr::JSXEmptyExpr(..) => { - syntax_error!(p, p.span(start), SyntaxError::EmptyJSXAttr) - } - JSXExpr::Expr(..) => Ok(node.into()), - #[cfg(swc_ast_unknown)] - _ => unreachable!(), - } -} - -/// Parses any type of JSX attribute value. -/// -/// TODO(kdy1): Change return type to JSXAttrValue -fn parse_jsx_attr_value<'a, P: Parser<'a>>(p: &mut P) -> PResult { - debug_assert!(p.input().syntax().jsx()); - trace_cur!(p, parse_jsx_attr_value); - - let start = p.cur_pos(); - - let cur = p.input().cur(); - if cur.is_lbrace() { - let node = parse_jsx_expr_container(p)?; - jsx_expr_container_to_jsx_attr_value(p, start, node) - } else if cur.is_str() { - Ok(JSXAttrValue::Str(parse_str_lit(p))) - } else if cur.is_jsx_tag_start() { - let expr = parse_jsx_element(p)?; - match expr { - Either::Left(n) => Ok(JSXAttrValue::JSXFragment(n)), - Either::Right(n) => Ok(JSXAttrValue::JSXElement(Box::new(n))), - } - } else { - let span = p.input().cur_span(); - syntax_error!(p, span, SyntaxError::InvalidJSXValue) - } -} - -/// Parse JSX spread child -fn parse_jsx_spread_child<'a, P: Parser<'a>>(p: &mut P) -> PResult { - debug_assert!(p.input().syntax().jsx()); - debug_assert!(p.input().cur().is_lbrace()); - debug_assert!(peek!(p).is_some_and(|peek| peek.is_dotdotdot())); - - let start = p.cur_pos(); - p.bump(); // bump "{" - p.bump(); // bump "..." - let expr = p.parse_expr()?; - expect!(p, &P::Token::RBRACE); - - Ok(JSXSpreadChild { - span: p.span(start), - expr, - }) -} - -/// Parses following JSX attribute name-value pair. -fn parse_jsx_attr<'a, P: Parser<'a>>(p: &mut P) -> PResult { - debug_assert!(p.input().syntax().jsx()); - let start = p.cur_pos(); - - debug_tracing!(p, "parse_jsx_attr"); - - if p.input_mut().eat(&P::Token::LBRACE) { - let dot3_start = p.cur_pos(); - expect!(p, &P::Token::DOTDOTDOT); - let dot3_token = p.span(dot3_start); - let expr = parse_assignment_expr(p)?; - expect!(p, &P::Token::RBRACE); - return Ok(SpreadElement { dot3_token, expr }.into()); - } - - let name = parse_jsx_namespaced_name(p)?; - let value = if p.input_mut().eat(&P::Token::EQUAL) { - p.do_outside_of_context( - Context::InCondExpr.union(Context::WillExpectColonForCond), - parse_jsx_attr_value, - ) - .map(Some)? - } else { - None - }; - - Ok(JSXAttr { - span: p.span(start), - name, - value, - } - .into()) -} - -/// Parses JSX opening tag starting after "<". -fn parse_jsx_opening_element_at<'a, P: Parser<'a>>( - p: &mut P, - start: BytePos, -) -> PResult> { - debug_assert!(p.input().syntax().jsx()); - - if p.input_mut().eat(&P::Token::JSX_TAG_END) { - return Ok(Either::Left(JSXOpeningFragment { - span: p.span(start), - })); - } - - let name = - p.do_outside_of_context(Context::ShouldNotLexLtOrGtAsType, parse_jsx_element_name)?; - parse_jsx_opening_element_after_name(p, start, name).map(Either::Right) -} - -#[inline(always)] -fn parse_jsx_attrs<'a, P: Parser<'a>>(p: &mut P) -> PResult> { - let mut attrs = Vec::with_capacity(8); - - while !p.input().cur().is_eof() { - trace_cur!(p, parse_jsx_opening__attrs_loop); - - let cur = p.input().cur(); - if cur.is_slash() || cur.is_jsx_tag_end() { - break; - } - - let attr = parse_jsx_attr(p)?; - attrs.push(attr); - } - - Ok(attrs) -} - -/// `jsxParseOpeningElementAfterName` -fn parse_jsx_opening_element_after_name<'a, P: Parser<'a>>( - p: &mut P, - start: BytePos, - name: JSXElementName, -) -> PResult { - debug_assert!(p.input().syntax().jsx()); - - let type_args = if p.input().syntax().typescript() && p.input().is(&P::Token::LESS) { - try_parse_ts(p, |p| { - let ret = parse_ts_type_args(p)?; - p.assert_and_bump(&P::Token::GREATER); - Ok(Some(ret)) - }) - } else { - None - }; - - let attrs = parse_jsx_attrs(p)?; - - let self_closing = p.input_mut().eat(&P::Token::DIV); - if !p.input_mut().eat(&P::Token::JSX_TAG_END) - & !(p.ctx().contains(Context::InForcedJsxContext) && p.input_mut().eat(&P::Token::GREATER)) - { - unexpected!(p, "> (jsx closing tag)"); - } - Ok(JSXOpeningElement { - span: p.span(start), - name, - attrs, - self_closing, - type_args, - }) -} - -/// Parses entire JSX element, including it"s opening tag -/// (starting after "<"), attributes, contents and closing tag. -/// -/// babel: `jsxParseElementAt` -fn parse_jsx_element_at<'a, P: Parser<'a>>( - p: &mut P, - start_pos: BytePos, -) -> PResult> { - debug_assert!(p.input().syntax().jsx()); - - let cur = p.input().cur(); - if cur.is_error() { - let error = p.input_mut().expect_error_token_and_bump(); - return Err(error); - } else if cur.is_eof() { - return Err(eof_error(p)); - } - let forced_jsx_context = if cur.is_less() { - true - } else { - debug_assert!(cur.is_jsx_tag_start()); - false - }; - let start = p.cur_pos(); - p.bump(); - - p.do_outside_of_context(Context::ShouldNotLexLtOrGtAsType, |p| { - let f = |p: &mut P| { - debug_tracing!(p, "parse_jsx_element"); - - let opening_element = parse_jsx_opening_element_at(p, start_pos)?; - - trace_cur!(p, parse_jsx_element__after_opening_element); - - let mut children = Vec::new(); - let mut closing_element = None; - - let self_closing = match opening_element { - Either::Right(ref el) => el.self_closing, - _ => false, - }; - - if !self_closing { - 'contents: loop { - let cur = p.input().cur(); - if cur.is_jsx_tag_start() { - let start = p.cur_pos(); - if peek!(p).is_some_and(|peek| peek.is_slash()) { - p.bump(); // JSXTagStart - if p.input().cur().is_eof() { - return Err(eof_error(p)); - } - p.assert_and_bump(&P::Token::DIV); - closing_element = parse_jsx_closing_element_at(p, start).map(Some)?; - break 'contents; - } - children.push(parse_jsx_element_at(p, start).map(|e| match e { - Either::Left(e) => JSXElementChild::from(e), - Either::Right(e) => JSXElementChild::from(Box::new(e)), - })?); - } else if cur.is_jsx_text() { - children.push(JSXElementChild::from(parse_jsx_text(p))) - } else if cur.is_lbrace() { - if peek!(p).is_some_and(|peek| peek.is_dotdotdot()) { - children.push(parse_jsx_spread_child(p).map(JSXElementChild::from)?); - } else { - children.push(parse_jsx_expr_container(p).map(JSXElementChild::from)?); - } - } else { - unexpected!(p, "< (jsx tag start), jsx text or {") - } - } - } - let span = p.span(start); - - Ok(match (opening_element, closing_element) { - (Either::Left(..), Some(Either::Right(closing))) => { - syntax_error!(p, closing.span(), SyntaxError::JSXExpectedClosingTagForLtGt); - } - (Either::Right(opening), Some(Either::Left(closing))) => { - syntax_error!( - p, - closing.span(), - SyntaxError::JSXExpectedClosingTag { - tag: get_qualified_jsx_name(&opening.name) - } - ); - } - (Either::Left(opening), Some(Either::Left(closing))) => Either::Left(JSXFragment { - span, - opening, - children, - closing, - }), - (Either::Right(opening), None) => Either::Right(JSXElement { - span, - opening, - children, - closing: None, - }), - (Either::Right(opening), Some(Either::Right(closing))) => { - if get_qualified_jsx_name(&closing.name) - != get_qualified_jsx_name(&opening.name) - { - syntax_error!( - p, - closing.span(), - SyntaxError::JSXExpectedClosingTag { - tag: get_qualified_jsx_name(&opening.name) - } - ); - } - Either::Right(JSXElement { - span, - opening, - children, - closing: Some(closing), - }) - } - _ => unreachable!(), - }) - }; - if forced_jsx_context { - p.do_inside_of_context(Context::InForcedJsxContext, f) - } else { - p.do_outside_of_context(Context::InForcedJsxContext, f) - } - }) -} - -/// Parses entire JSX element from current position. -/// -/// babel: `jsxParseElement` -pub(crate) fn parse_jsx_element<'a, P: Parser<'a>>( - p: &mut P, -) -> PResult> { - trace_cur!(p, parse_jsx_element); - - debug_assert!(p.input().syntax().jsx()); - debug_assert!({ - let cur = p.input().cur(); - cur.is_jsx_tag_start() || cur.is_less() - }); - - let start_pos = p.cur_pos(); - - p.do_outside_of_context( - Context::InCondExpr.union(Context::WillExpectColonForCond), - |p| parse_jsx_element_at(p, start_pos), - ) -} diff --git a/crates/swc_ecma_lexer/src/common/parser/macros.rs b/crates/swc_ecma_lexer/src/common/parser/macros.rs deleted file mode 100644 index 5e07135040a7..000000000000 --- a/crates/swc_ecma_lexer/src/common/parser/macros.rs +++ /dev/null @@ -1,103 +0,0 @@ -macro_rules! unexpected { - ($p:expr, $expected:literal) => {{ - let got = $p.input_mut().dump_cur(); - syntax_error!( - $p, - $p.input().cur_span(), - SyntaxError::Unexpected { - got, - expected: $expected - } - ) - }}; -} - -macro_rules! expect { - ($p:expr, $t:expr) => {{ - if !$p.input_mut().eat($t) { - let span = $p.input().cur_span(); - let cur = $p.input_mut().dump_cur(); - syntax_error!($p, span, SyntaxError::Expected(format!("{:?}", $t), cur)) - } - }}; -} - -macro_rules! syntax_error { - ($p:expr, $err:expr) => { - syntax_error!($p, $p.input().cur_span(), $err) - }; - ($p:expr, $span:expr, $err:expr) => {{ - let err = $crate::error::Error::new($span, $err); - { - let cur = $p.input().cur(); - if cur.is_error() { - let error = $p.input_mut().expect_error_token_and_bump(); - $p.emit_error(error); - } - } - if cfg!(feature = "debug") { - #[cfg(debug_assertions)] - tracing::error!( - "Syntax error called from {}:{}:{}\nCurrent token = {:?}", - file!(), - line!(), - column!(), - $p.input().cur() - ); - } - return Err(err.into()); - }}; -} - -macro_rules! peek { - ($p:expr) => {{ - debug_assert!( - !$p.input().cur().is_eof(), - "parser should not call peek() without knowing current token. -Current token is {:?}", - $p.input().cur(), - ); - $p.input_mut().peek() - }}; -} - -macro_rules! trace_cur { - ($p:expr, $name:ident) => {{ - if cfg!(feature = "debug") { - #[cfg(debug_assertions)] - tracing::debug!("{}: {:?}", stringify!($name), $p.input().cur()); - } - }}; -} - -macro_rules! debug_tracing { - ($p:expr, $name:tt) => {{ - #[cfg(all(debug_assertions, feature = "debug"))] - { - tracing::span!( - tracing::Level::ERROR, - $name, - cur = tracing::field::debug(&$p.input.cur()) - ) - .entered() - } - }}; -} - -macro_rules! return_if_arrow { - ($p:expr, $expr:expr) => {{ - // FIXME: - // - // - - // let is_cur = match $p.state.potential_arrow_start { - // Some(start) => $expr.span.lo() == start, - // None => false - // }; - // if is_cur { - if let Expr::Arrow { .. } = *$expr { - return Ok($expr); - } - // } - }}; -} diff --git a/crates/swc_ecma_lexer/src/common/parser/mod.rs b/crates/swc_ecma_lexer/src/common/parser/mod.rs deleted file mode 100644 index 869669eb4283..000000000000 --- a/crates/swc_ecma_lexer/src/common/parser/mod.rs +++ /dev/null @@ -1,518 +0,0 @@ -use either::Either; -use expr::{parse_assignment_expr, parse_str_lit}; -use expr_ext::ExprExt; -use swc_atoms::Atom; -use swc_common::{BytePos, Span, Spanned}; -use swc_ecma_ast::*; - -use self::{ - buffer::{Buffer, NextTokenAndSpan}, - state::{State, WithState}, - token_and_span::TokenAndSpan, -}; -use super::{context::Context, input::Tokens, lexer::token::TokenFactory}; -use crate::{ - common::syntax::SyntaxFlags, - error::{Error, SyntaxError}, -}; - -pub type PResult = Result; - -pub mod buffer; -pub mod expr_ext; -pub mod is_directive; -pub mod is_invalid_class_name; -pub mod is_simple_param_list; -#[macro_use] -mod macros; -pub mod assign_target_or_spread; -pub mod class_and_fn; -pub mod expr; -pub mod ident; -pub mod jsx; -pub mod module_item; -pub mod object; -pub mod output_type; -pub mod pat; -pub mod pat_type; -pub mod state; -pub mod stmt; -pub mod token_and_span; -pub mod typescript; -mod util; -#[cfg(feature = "verify")] -pub mod verifier; - -pub use util::{ - get_qualified_jsx_name, has_use_strict, is_constructor, is_not_this, make_decl_declare, - unwrap_ts_non_null, -}; - -pub trait Parser<'a>: Sized + Clone { - type Token: std::fmt::Debug - + Clone - + TokenFactory<'a, Self::TokenAndSpan, Self::I, Buffer = Self::Buffer>; - type Next: NextTokenAndSpan; - type TokenAndSpan: TokenAndSpan; - type I: Tokens; - type Buffer: self::buffer::Buffer< - 'a, - Token = Self::Token, - TokenAndSpan = Self::TokenAndSpan, - I = Self::I, - >; - type Checkpoint; - - fn input(&self) -> &Self::Buffer; - fn input_mut(&mut self) -> &mut Self::Buffer; - fn state(&self) -> &State; - fn state_mut(&mut self) -> &mut State; - fn checkpoint_save(&self) -> Self::Checkpoint; - fn checkpoint_load(&mut self, checkpoint: Self::Checkpoint); - - #[inline(always)] - fn with_state<'w>(&'w mut self, state: State) -> WithState<'a, 'w, Self> { - let orig_state = std::mem::replace(self.state_mut(), state); - WithState { - orig_state, - inner: self, - marker: std::marker::PhantomData, - } - } - - #[inline(always)] - fn ctx(&self) -> Context { - self.input().get_ctx() - } - - #[inline(always)] - fn set_ctx(&mut self, ctx: Context) { - self.input_mut().set_ctx(ctx); - } - - #[inline] - fn do_inside_of_context(&mut self, context: Context, f: impl FnOnce(&mut Self) -> T) -> T { - let ctx = self.ctx(); - let inserted = ctx.complement().intersection(context); - if inserted.is_empty() { - f(self) - } else { - self.input_mut().update_ctx(|ctx| ctx.insert(inserted)); - let result = f(self); - self.input_mut().update_ctx(|ctx| ctx.remove(inserted)); - result - } - } - - fn do_outside_of_context(&mut self, context: Context, f: impl FnOnce(&mut Self) -> T) -> T { - let ctx = self.ctx(); - let removed = ctx.intersection(context); - if !removed.is_empty() { - self.input_mut().update_ctx(|ctx| ctx.remove(removed)); - let result = f(self); - self.input_mut().update_ctx(|ctx| ctx.insert(removed)); - result - } else { - f(self) - } - } - - #[inline(always)] - fn strict_mode(&mut self, f: impl FnOnce(&mut Self) -> T) -> T { - self.do_inside_of_context(Context::Strict, f) - } - - /// Original context is restored when returned guard is dropped. - #[inline(always)] - fn in_type(&mut self, f: impl FnOnce(&mut Self) -> T) -> T { - self.do_inside_of_context(Context::InType, f) - } - - #[inline(always)] - fn allow_in_expr(&mut self, f: impl FnOnce(&mut Self) -> T) -> T { - self.do_inside_of_context(Context::IncludeInExpr, f) - } - - #[inline(always)] - fn disallow_in_expr(&mut self, f: impl FnOnce(&mut Self) -> T) -> T { - self.do_outside_of_context(Context::IncludeInExpr, f) - } - - #[inline(always)] - fn syntax(&self) -> SyntaxFlags { - self.input().syntax() - } - - #[cold] - fn emit_err(&mut self, span: Span, error: SyntaxError) { - if self.ctx().contains(Context::IgnoreError) || !self.syntax().early_errors() { - return; - } - self.emit_error(crate::error::Error::new(span, error)) - } - - #[cold] - fn emit_error(&mut self, error: crate::error::Error) { - if self.ctx().contains(Context::IgnoreError) || !self.syntax().early_errors() { - return; - } - let cur = self.input().cur(); - if cur.is_error() { - let err = self.input_mut().expect_error_token_and_bump(); - self.input_mut().iter_mut().add_error(err); - } - self.input_mut().iter_mut().add_error(error); - } - - #[cold] - fn emit_strict_mode_err(&mut self, span: Span, error: SyntaxError) { - if self.ctx().contains(Context::IgnoreError) { - return; - } - let error = crate::error::Error::new(span, error); - if self.ctx().contains(Context::Strict) { - self.input_mut().iter_mut().add_error(error); - } else { - self.input_mut().iter_mut().add_module_mode_error(error); - } - } - - fn verify_expr(&mut self, expr: Box) -> PResult> { - #[cfg(feature = "verify")] - { - use swc_ecma_visit::Visit; - let mut v = self::verifier::Verifier { errors: Vec::new() }; - v.visit_expr(&expr); - for (span, error) in v.errors { - self.emit_err(span, error); - } - } - Ok(expr) - } - - #[inline(always)] - fn cur_pos(&self) -> BytePos { - self.input().cur_pos() - } - - #[inline(always)] - fn last_pos(&self) -> BytePos { - self.input().prev_span().hi - } - - #[inline] - fn is_general_semi(&mut self) -> bool { - let cur = self.input().cur(); - cur.is_semi() || cur.is_rbrace() || cur.is_eof() || self.input().had_line_break_before_cur() - } - - fn eat_general_semi(&mut self) -> bool { - if cfg!(feature = "debug") { - #[cfg(debug_assertions)] - tracing::trace!("eat(';'): cur={:?}", self.input().cur()); - } - let cur = self.input().cur(); - if cur.is_semi() { - self.bump(); - true - } else { - cur.is_rbrace() || self.input().had_line_break_before_cur() || cur.is_eof() - } - } - - #[inline] - fn expect_general_semi(&mut self) -> PResult<()> { - if !self.eat_general_semi() { - let span = self.input().cur_span(); - let cur = self.input_mut().dump_cur(); - syntax_error!(self, span, SyntaxError::Expected(";".to_string(), cur)) - } - Ok(()) - } - - #[inline] - fn expect(&mut self, t: &Self::Token) -> PResult<()> { - if !self.input_mut().eat(t) { - let span = self.input().cur_span(); - let cur = self.input_mut().dump_cur(); - syntax_error!(self, span, SyntaxError::Expected(format!("{t:?}"), cur)) - } else { - Ok(()) - } - } - - #[inline(always)] - fn expect_without_advance(&mut self, t: &Self::Token) -> PResult<()> { - if !self.input_mut().is(t) { - let span = self.input().cur_span(); - let cur = self.input_mut().dump_cur(); - syntax_error!(self, span, SyntaxError::Expected(format!("{t:?}"), cur)) - } else { - Ok(()) - } - } - - #[inline(always)] - fn bump(&mut self) { - debug_assert!( - !self.input().cur().is_eof(), - "parser should not call bump() without knowing current token" - ); - self.input_mut().bump() - } - - #[inline] - fn span(&self, start: BytePos) -> Span { - let end = self.last_pos(); - debug_assert!( - start <= end, - "assertion failed: (span.start <= span.end). start = {start:?}, end = {end:?}", - ); - Span::new_with_checked(start, end) - } - - #[inline(always)] - fn assert_and_bump(&mut self, token: &Self::Token) { - debug_assert!( - self.input().is(token), - "assertion failed: expected {token:?}, got {:?}", - self.input().cur() - ); - self.bump(); - } - - fn check_assign_target(&mut self, expr: &Expr, deny_call: bool) { - if !expr.is_valid_simple_assignment_target(self.ctx().contains(Context::Strict)) { - self.emit_err(expr.span(), SyntaxError::TS2406); - } - - // We follow behavior of tsc - if self.input().syntax().typescript() && self.syntax().early_errors() { - let is_eval_or_arguments = match expr { - Expr::Ident(i) => i.is_reserved_in_strict_bind(), - _ => false, - }; - - if is_eval_or_arguments { - self.emit_strict_mode_err(expr.span(), SyntaxError::TS1100); - } - - fn should_deny(e: &Expr, deny_call: bool) -> bool { - match e { - Expr::Lit(..) => false, - Expr::Call(..) => deny_call, - Expr::Bin(..) => false, - Expr::Paren(ref p) => should_deny(&p.expr, deny_call), - - _ => true, - } - } - - // It is an early Reference Error if LeftHandSideExpression is neither - // an ObjectLiteral nor an ArrayLiteral and - // IsValidSimpleAssignmentTarget of LeftHandSideExpression is false. - if !is_eval_or_arguments - && !expr.is_valid_simple_assignment_target(self.ctx().contains(Context::Strict)) - && should_deny(expr, deny_call) - { - self.emit_err(expr.span(), SyntaxError::TS2406); - } - } - } - - fn parse_tpl_element(&mut self, is_tagged_tpl: bool) -> PResult { - let start = self.cur_pos(); - let cur = self.input().cur(); - let (raw, cooked) = if cur.is_template() { - let (cooked, raw) = self.input_mut().expect_template_token_and_bump(); - match cooked { - Ok(cooked) => (raw, Some(cooked)), - Err(err) => { - if is_tagged_tpl { - (raw, None) - } else { - return Err(err); - } - } - } - } else { - unexpected!(self, "template token") - }; - let tail = self.input_mut().is(&Self::Token::BACKQUOTE); - Ok(TplElement { - span: self.span(start), - raw, - tail, - cooked, - }) - } - - /// spec: 'PropertyName' - fn parse_prop_name(&mut self) -> PResult { - trace_cur!(self, parse_prop_name); - self.do_inside_of_context(Context::InPropertyName, |p| { - let start = p.input().cur_pos(); - let cur = p.input().cur(); - let v = if cur.is_str() { - PropName::Str(parse_str_lit(p)) - } else if cur.is_num() { - let (value, raw) = p.input_mut().expect_number_token_and_bump(); - PropName::Num(Number { - span: p.span(start), - value, - raw: Some(raw), - }) - } else if cur.is_bigint() { - let (value, raw) = p.input_mut().expect_bigint_token_and_bump(); - PropName::BigInt(BigInt { - span: p.span(start), - value, - raw: Some(raw), - }) - } else if cur.is_word() { - let w = p.input_mut().expect_word_token_and_bump(); - PropName::Ident(IdentName::new(w, p.span(start))) - } else if cur.is_lbracket() { - p.bump(); - let inner_start = p.input().cur_pos(); - let mut expr = p.allow_in_expr(parse_assignment_expr)?; - if p.syntax().typescript() && p.input().is(&Self::Token::COMMA) { - let mut exprs = vec![expr]; - while p.input_mut().eat(&Self::Token::COMMA) { - // - exprs.push(p.allow_in_expr(parse_assignment_expr)?); - } - p.emit_err(p.span(inner_start), SyntaxError::TS1171); - expr = Box::new( - SeqExpr { - span: p.span(inner_start), - exprs, - } - .into(), - ); - } - expect!(p, &Self::Token::RBRACKET); - PropName::Computed(ComputedPropName { - span: p.span(start), - expr, - }) - } else { - unexpected!( - p, - "identifier, string literal, numeric literal or [ for the computed key" - ) - }; - Ok(v) - }) - } - - /// AssignmentExpression[+In, ?Yield, ?Await] - /// ...AssignmentExpression[+In, ?Yield, ?Await] - fn parse_expr_or_spread(&mut self) -> PResult { - trace_cur!(self, parse_expr_or_spread); - let start = self.input().cur_pos(); - if self.input_mut().eat(&Self::Token::DOTDOTDOT) { - let spread_span = self.span(start); - let spread = Some(spread_span); - self.allow_in_expr(parse_assignment_expr) - .map_err(|err| { - Error::new( - err.span(), - SyntaxError::WithLabel { - inner: Box::new(err), - span: spread_span, - note: "An expression should follow '...'", - }, - ) - }) - .map(|expr| ExprOrSpread { spread, expr }) - } else { - parse_assignment_expr(self).map(|expr| ExprOrSpread { spread: None, expr }) - } - } - - fn parse_expr(&mut self) -> PResult> { - trace_cur!(self, parse_expr); - debug_tracing!(self, "parse_expr"); - let expr = parse_assignment_expr(self)?; - let start = expr.span_lo(); - - if self.input_mut().is(&Self::Token::COMMA) { - let mut exprs = vec![expr]; - - while self.input_mut().eat(&Self::Token::COMMA) { - exprs.push(parse_assignment_expr(self)?); - } - - return Ok(SeqExpr { - span: self.span(start), - exprs, - } - .into()); - } - - Ok(expr) - } - - fn mark_found_module_item(&mut self); - - #[inline] - fn is_ident_ref(&mut self) -> bool { - let cur = self.input().cur(); - cur.is_word() && !cur.is_reserved(self.ctx()) - } - - #[inline] - fn peek_is_ident_ref(&mut self) -> bool { - let ctx = self.ctx(); - peek!(self).is_some_and(|peek| peek.is_word() && !peek.is_reserved(ctx)) - } - - #[inline(always)] - fn eat_ident_ref(&mut self) -> bool { - if self.is_ident_ref() { - self.bump(); - true - } else { - false - } - } - - fn ts_in_no_context(&mut self, op: impl FnOnce(&mut Self) -> PResult) -> PResult; - - fn parse_jsx_element( - &mut self, - in_expr_context: bool, - ) -> PResult>; - fn parse_primary_expr(&mut self) -> PResult>; - fn parse_unary_expr(&mut self) -> PResult>; - fn parse_tagged_tpl( - &mut self, - tag: Box, - type_params: Option>, - ) -> PResult; - fn parse_tagged_tpl_ty(&mut self) -> PResult; - fn parse_lhs_expr(&mut self) -> PResult>; -} - -pub fn parse_shebang<'a>(p: &mut impl Parser<'a>) -> PResult> { - let cur = p.input().cur(); - Ok(if cur.is_shebang() { - let ret = p.input_mut().expect_shebang_token_and_bump(); - Some(ret) - } else { - None - }) -} - -#[cold] -#[inline(never)] -pub fn eof_error<'a, P: Parser<'a>>(p: &mut P) -> crate::error::Error { - debug_assert!( - p.input().cur().is_eof(), - "Parser should not call throw_eof_error() without knowing current token" - ); - let pos = p.input().end_pos(); - let last = Span { lo: pos, hi: pos }; - crate::error::Error::new(last, crate::error::SyntaxError::Eof) -} diff --git a/crates/swc_ecma_lexer/src/common/parser/module_item.rs b/crates/swc_ecma_lexer/src/common/parser/module_item.rs deleted file mode 100644 index ad8e68f7ea96..000000000000 --- a/crates/swc_ecma_lexer/src/common/parser/module_item.rs +++ /dev/null @@ -1,930 +0,0 @@ -use swc_atoms::atom; -use swc_common::Span; -use swc_ecma_ast::*; - -use super::{ - buffer::Buffer, - class_and_fn::{parse_default_async_fn, parse_default_fn, parse_fn_decl}, - expr::parse_assignment_expr, - ident::{parse_ident, parse_ident_name, parse_module_export_name}, - stmt::{parse_block_body, parse_stmt_like, parse_var_stmt}, - typescript::{parse_ts_import_equals_decl, try_parse_ts_declare, try_parse_ts_export_decl}, - PResult, Parser, -}; -use crate::{ - common::{ - context::Context, - lexer::token::TokenFactory, - parser::{ - class_and_fn::{ - parse_async_fn_decl, parse_class_decl, parse_decorators, parse_default_class, - }, - eof_error, - expr::parse_str_lit, - ident::parse_binding_ident, - object::parse_object_expr, - typescript::{parse_ts_enum_decl, parse_ts_interface_decl}, - }, - }, - error::SyntaxError, -}; - -fn handle_import_export<'a, P: Parser<'a>>( - p: &mut P, - decorators: Vec, -) -> PResult { - if !p - .ctx() - .intersects(Context::TopLevel.union(Context::TsModuleBlock)) - { - syntax_error!(p, SyntaxError::NonTopLevelImportExport); - } - - let decl = if p.input().is(&P::Token::IMPORT) { - parse_import(p)? - } else if p.input().is(&P::Token::EXPORT) { - parse_export(p, decorators).map(ModuleItem::from)? - } else { - unreachable!( - "handle_import_export should not be called if current token isn't import nor export" - ) - }; - - Ok(decl) -} - -pub fn parse_module_item_block_body<'a, P: Parser<'a>>( - p: &mut P, - allow_directives: bool, - end: Option<&P::Token>, -) -> PResult> { - parse_block_body(p, allow_directives, end, handle_import_export) -} - -/// Parses `from 'foo.js' with {};` or `from 'foo.js' assert {};` -fn parse_from_clause_and_semi<'a, P: Parser<'a>>( - p: &mut P, -) -> PResult<(Box, Option>)> { - expect!(p, &P::Token::FROM); - - let cur = p.input().cur(); - let src = if cur.is_str() { - Box::new(parse_str_lit(p)) - } else { - unexpected!(p, "a string literal") - }; - let with = if p.input().syntax().import_attributes() - && !p.input().had_line_break_before_cur() - && (p.input_mut().eat(&P::Token::ASSERT) || p.input_mut().eat(&P::Token::WITH)) - { - match parse_object_expr(p)? { - Expr::Object(v) => Some(Box::new(v)), - _ => unreachable!(), - } - } else { - None - }; - p.expect_general_semi()?; - Ok((src, with)) -} - -fn parse_named_export_specifier<'a, P: Parser<'a>>( - p: &mut P, - type_only: bool, -) -> PResult { - let start = p.cur_pos(); - - let mut is_type_only = false; - - let orig = match parse_module_export_name(p)? { - ModuleExportName::Ident(orig_ident) => { - // Handle: - // `export { type xx }` - // `export { type xx as yy }` - // `export { type as }` - // `export { type as as }` - // `export { type as as as }` - if p.syntax().typescript() && orig_ident.sym == "type" && p.input().cur().is_word() { - let possibly_orig = parse_ident_name(p).map(Ident::from)?; - if possibly_orig.sym == "as" { - // `export { type as }` - if !p.input().cur().is_word() { - if type_only { - p.emit_err(orig_ident.span, SyntaxError::TS2207); - } - - return Ok(ExportNamedSpecifier { - span: p.span(start), - orig: ModuleExportName::Ident(possibly_orig), - exported: None, - is_type_only: true, - }); - } - - let maybe_as = parse_ident_name(p).map(Ident::from)?; - if maybe_as.sym == "as" { - if p.input().cur().is_word() { - // `export { type as as as }` - // `export { type as as foo }` - let exported = parse_ident_name(p).map(Ident::from)?; - - if type_only { - p.emit_err(orig_ident.span, SyntaxError::TS2207); - } - - debug_assert!(start <= orig_ident.span.hi()); - return Ok(ExportNamedSpecifier { - span: Span::new_with_checked(start, orig_ident.span.hi()), - orig: ModuleExportName::Ident(possibly_orig), - exported: Some(ModuleExportName::Ident(exported)), - is_type_only: true, - }); - } else { - // `export { type as as }` - return Ok(ExportNamedSpecifier { - span: Span::new_with_checked(start, orig_ident.span.hi()), - orig: ModuleExportName::Ident(orig_ident), - exported: Some(ModuleExportName::Ident(maybe_as)), - is_type_only: false, - }); - } - } else { - // `export { type as xxx }` - return Ok(ExportNamedSpecifier { - span: Span::new_with_checked(start, orig_ident.span.hi()), - orig: ModuleExportName::Ident(orig_ident), - exported: Some(ModuleExportName::Ident(maybe_as)), - is_type_only: false, - }); - } - } else { - // `export { type xx }` - // `export { type xx as yy }` - if type_only { - p.emit_err(orig_ident.span, SyntaxError::TS2207); - } - - is_type_only = true; - ModuleExportName::Ident(possibly_orig) - } - } else { - ModuleExportName::Ident(orig_ident) - } - } - module_export_name => module_export_name, - }; - - let exported = if p.input_mut().eat(&P::Token::AS) { - Some(parse_module_export_name(p)?) - } else { - None - }; - - Ok(ExportNamedSpecifier { - span: p.span(start), - orig, - exported, - is_type_only, - }) -} - -fn parse_imported_binding<'a>(p: &mut impl Parser<'a>) -> PResult { - Ok( - p.do_outside_of_context(Context::InAsync.union(Context::InGenerator), |p| { - parse_binding_ident(p, false) - })? - .into(), - ) -} - -fn parse_imported_default_binding<'a>(p: &mut impl Parser<'a>) -> PResult { - parse_imported_binding(p) -} - -/// Parse `foo`, `foo2 as bar` in `import { foo, foo2 as bar }` -fn parse_import_specifier<'a, P: Parser<'a>>( - p: &mut P, - type_only: bool, -) -> PResult { - let start = p.cur_pos(); - match parse_module_export_name(p)? { - ModuleExportName::Ident(mut orig_name) => { - let mut is_type_only = false; - // Handle: - // `import { type xx } from 'mod'` - // `import { type xx as yy } from 'mod'` - // `import { type as } from 'mod'` - // `import { type as as } from 'mod'` - // `import { type as as as } from 'mod'` - if p.syntax().typescript() && orig_name.sym == "type" && p.input().cur().is_word() { - let possibly_orig_name = parse_ident_name(p).map(Ident::from)?; - if possibly_orig_name.sym == "as" { - // `import { type as } from 'mod'` - if !p.input().cur().is_word() { - if p.ctx().is_reserved_word(&possibly_orig_name.sym) { - syntax_error!( - p, - possibly_orig_name.span, - SyntaxError::ReservedWordInImport - ) - } - - if type_only { - p.emit_err(orig_name.span, SyntaxError::TS2206); - } - - return Ok(ImportSpecifier::Named(ImportNamedSpecifier { - span: p.span(start), - local: possibly_orig_name, - imported: None, - is_type_only: true, - })); - } - - let maybe_as: Ident = parse_binding_ident(p, false)?.into(); - if maybe_as.sym == "as" { - if p.input().cur().is_word() { - // `import { type as as as } from 'mod'` - // `import { type as as foo } from 'mod'` - let local: Ident = parse_binding_ident(p, false)?.into(); - - if type_only { - p.emit_err(orig_name.span, SyntaxError::TS2206); - } - - return Ok(ImportSpecifier::Named(ImportNamedSpecifier { - span: Span::new_with_checked(start, orig_name.span.hi()), - local, - imported: Some(ModuleExportName::Ident(possibly_orig_name)), - is_type_only: true, - })); - } else { - // `import { type as as } from 'mod'` - return Ok(ImportSpecifier::Named(ImportNamedSpecifier { - span: Span::new_with_checked(start, maybe_as.span.hi()), - local: maybe_as, - imported: Some(ModuleExportName::Ident(orig_name)), - is_type_only: false, - })); - } - } else { - // `import { type as xxx } from 'mod'` - return Ok(ImportSpecifier::Named(ImportNamedSpecifier { - span: Span::new_with_checked(start, orig_name.span.hi()), - local: maybe_as, - imported: Some(ModuleExportName::Ident(orig_name)), - is_type_only: false, - })); - } - } else { - // `import { type xx } from 'mod'` - // `import { type xx as yy } from 'mod'` - if type_only { - p.emit_err(orig_name.span, SyntaxError::TS2206); - } - - orig_name = possibly_orig_name; - is_type_only = true; - } - } - - if p.input_mut().eat(&P::Token::AS) { - let local: Ident = parse_binding_ident(p, false)?.into(); - return Ok(ImportSpecifier::Named(ImportNamedSpecifier { - span: Span::new_with_checked(start, local.span.hi()), - local, - imported: Some(ModuleExportName::Ident(orig_name)), - is_type_only, - })); - } - - // Handle difference between - // - // 'ImportedBinding' - // 'IdentifierName' as 'ImportedBinding' - if p.ctx().is_reserved_word(&orig_name.sym) { - syntax_error!(p, orig_name.span, SyntaxError::ReservedWordInImport) - } - - let local = orig_name; - Ok(ImportSpecifier::Named(ImportNamedSpecifier { - span: p.span(start), - local, - imported: None, - is_type_only, - })) - } - ModuleExportName::Str(orig_str) => { - if p.input_mut().eat(&P::Token::AS) { - let local: Ident = parse_binding_ident(p, false)?.into(); - Ok(ImportSpecifier::Named(ImportNamedSpecifier { - span: Span::new_with_checked(start, local.span.hi()), - local, - imported: Some(ModuleExportName::Str(orig_str)), - is_type_only: false, - })) - } else { - syntax_error!( - p, - orig_str.span, - SyntaxError::ImportBindingIsString(orig_str.value.to_string_lossy().into()) - ) - } - } - - #[cfg(swc_ast_unknown)] - _ => unreachable!(), - } -} - -fn parse_export<'a, P: Parser<'a>>( - p: &mut P, - mut decorators: Vec, -) -> PResult { - if !p.ctx().contains(Context::Module) && p.ctx().contains(Context::TopLevel) { - // Switch to module mode - let ctx = p.ctx() | Context::Module | Context::Strict; - p.set_ctx(ctx); - } - - let start = p.cur_pos(); - p.assert_and_bump(&P::Token::EXPORT); - - let cur = p.input().cur(); - if cur.is_eof() { - return Err(eof_error(p)); - } - - let after_export_start = p.cur_pos(); - - // "export declare" is equivalent to just "export". - let declare = p.input().syntax().typescript() && p.input_mut().eat(&P::Token::DECLARE); - - if declare { - // TODO: Remove - if let Some(decl) = try_parse_ts_declare(p, after_export_start, decorators.clone())? { - return Ok(ExportDecl { - span: p.span(start), - decl, - } - .into()); - } - } - - if p.input().syntax().typescript() { - let cur = p.input().cur(); - if cur.is_word() { - let sym = cur.clone().take_word(p.input()).unwrap(); - // TODO: remove clone - if let Some(decl) = try_parse_ts_export_decl(p, decorators.clone(), sym) { - return Ok(ExportDecl { - span: p.span(start), - decl, - } - .into()); - } - } - - if p.input_mut().eat(&P::Token::IMPORT) { - let is_type_only = - p.input().is(&P::Token::TYPE) && peek!(p).is_some_and(|p| p.is_word()); - - if is_type_only { - p.assert_and_bump(&P::Token::TYPE); - } - - let id = parse_ident_name(p)?; - - // export import A = B - return parse_ts_import_equals_decl( - p, - start, - id.into(), - /* is_export */ true, - is_type_only, - ) - .map(From::from); - } - - if p.input_mut().eat(&P::Token::EQUAL) { - // `export = x;` - let expr = p.parse_expr()?; - p.expect_general_semi()?; - return Ok(TsExportAssignment { - span: p.span(start), - expr, - } - .into()); - } - - if p.input_mut().eat(&P::Token::AS) { - // `export as namespace A;` - // See `parseNamespaceExportDeclaration` in TypeScript's own parser - expect!(p, &P::Token::NAMESPACE); - let id = parse_ident(p, false, false)?; - p.expect_general_semi()?; - return Ok(TsNamespaceExportDecl { - span: p.span(start), - id, - } - .into()); - } - } - - let ns_export_specifier_start = p.cur_pos(); - - let type_only = p.input().syntax().typescript() && p.input_mut().eat(&P::Token::TYPE); - - // Some("default") if default is exported from 'src' - let mut export_default = None; - - if !type_only && p.input_mut().eat(&P::Token::DEFAULT) { - if p.input().is(&P::Token::AT) { - let start = p.cur_pos(); - let after_decorators = parse_decorators(p, false)?; - - if !decorators.is_empty() { - syntax_error!(p, p.span(start), SyntaxError::TS8038); - } - - decorators = after_decorators; - } - - if p.input().syntax().typescript() { - if p.input().is(&P::Token::ABSTRACT) - && peek!(p).is_some_and(|cur| cur.is_class()) - && !p.input_mut().has_linebreak_between_cur_and_peeked() - { - let class_start = p.cur_pos(); - p.assert_and_bump(&P::Token::ABSTRACT); - let cur = p.input().cur(); - if cur.is_error() { - let err = p.input_mut().expect_error_token_and_bump(); - return Err(err); - } - - return parse_default_class(p, start, class_start, decorators, true) - .map(ModuleDecl::ExportDefaultDecl); - } - if p.input().is(&P::Token::ABSTRACT) && peek!(p).is_some_and(|cur| cur.is_interface()) { - p.emit_err(p.input().cur_span(), SyntaxError::TS1242); - p.assert_and_bump(&P::Token::ABSTRACT); - } - - if p.input().is(&P::Token::INTERFACE) { - let interface_start = p.cur_pos(); - p.assert_and_bump(&P::Token::INTERFACE); - let decl = parse_ts_interface_decl(p, interface_start).map(DefaultDecl::from)?; - return Ok(ExportDefaultDecl { - span: p.span(start), - decl, - } - .into()); - } - } - - if p.input().is(&P::Token::CLASS) { - let class_start = p.cur_pos(); - let decl = parse_default_class(p, start, class_start, decorators, false)?; - return Ok(decl.into()); - } else if p.input().is(&P::Token::ASYNC) - && peek!(p).is_some_and(|cur| cur.is_function()) - && !p.input_mut().has_linebreak_between_cur_and_peeked() - { - let decl = parse_default_async_fn(p, start, decorators)?; - return Ok(decl.into()); - } else if p.input().is(&P::Token::FUNCTION) { - let decl = parse_default_fn(p, start, decorators)?; - return Ok(decl.into()); - } else if p.input().syntax().export_default_from() - && ((p.input().is(&P::Token::FROM) && peek!(p).is_some_and(|peek| peek.is_str())) - || (p.input().is(&P::Token::COMMA) - && (peek!(p).is_some_and(|peek| peek.is_star() || peek.is_lbrace())))) - { - export_default = Some(Ident::new_no_ctxt(atom!("default"), p.input().prev_span())) - } else { - let expr = p.allow_in_expr(parse_assignment_expr)?; - p.expect_general_semi()?; - return Ok(ExportDefaultExpr { - span: p.span(start), - expr, - } - .into()); - } - } - - if p.input().is(&P::Token::AT) { - let start = p.cur_pos(); - let after_decorators = parse_decorators(p, false)?; - - if !decorators.is_empty() { - syntax_error!(p, p.span(start), SyntaxError::TS8038); - } - - decorators = after_decorators; - } - - let decl = if !type_only && p.input().is(&P::Token::CLASS) { - let class_start = p.cur_pos(); - parse_class_decl(p, start, class_start, decorators, false)? - } else if !type_only - && p.input().is(&P::Token::ASYNC) - && peek!(p).is_some_and(|cur| cur.is_function()) - && !p.input_mut().has_linebreak_between_cur_and_peeked() - { - parse_async_fn_decl(p, decorators)? - } else if !type_only && p.input().is(&P::Token::FUNCTION) { - parse_fn_decl(p, decorators)? - } else if !type_only - && p.input().syntax().typescript() - && p.input().is(&P::Token::CONST) - && peek!(p).is_some_and(|cur| cur.is_enum()) - { - let enum_start = p.cur_pos(); - p.assert_and_bump(&P::Token::CONST); - p.assert_and_bump(&P::Token::ENUM); - return parse_ts_enum_decl(p, enum_start, /* is_const */ true) - .map(Decl::from) - .map(|decl| { - ExportDecl { - span: p.span(start), - decl, - } - .into() - }); - } else if !type_only - && (p.input().is(&P::Token::VAR) - || p.input().is(&P::Token::CONST) - || (p.input().is(&P::Token::LET)) - && peek!(p).map(|t| t.follows_keyword_let()).unwrap_or(false)) - { - parse_var_stmt(p, false).map(Decl::Var)? - } else { - // ```javascript - // export foo, * as bar, { baz } from "mod"; // * - // export * as bar, { baz } from "mod"; // * - // export foo, { baz } from "mod"; // * - // export foo, * as bar from "mod"; // * - // export foo from "mod"; // * - // export * as bar from "mod"; // - // export { baz } from "mod"; // - // export { baz } ; // - // export * from "mod"; // - // ``` - - // export default - // export foo - let default = match export_default { - Some(default) => Some(default), - None => { - if p.input().syntax().export_default_from() && p.input().cur().is_word() { - Some(parse_ident(p, false, false)?) - } else { - None - } - } - }; - - if default.is_none() - && p.input().is(&P::Token::MUL) - && !peek!(p).is_some_and(|cur| cur.is_as()) - { - p.assert_and_bump(&P::Token::MUL); - - // improve error message for `export * from foo` - let (src, with) = parse_from_clause_and_semi(p)?; - return Ok(ExportAll { - span: p.span(start), - src, - type_only, - with, - } - .into()); - } - - let mut specifiers = Vec::new(); - - let mut has_default = false; - let mut has_ns = false; - - if let Some(default) = default { - has_default = true; - specifiers.push(ExportSpecifier::Default(ExportDefaultSpecifier { - exported: default, - })) - } - - // export foo, * as bar - // ^ - if !specifiers.is_empty() - && p.input().is(&P::Token::COMMA) - && peek!(p).is_some_and(|cur| cur.is_star()) - { - p.assert_and_bump(&P::Token::COMMA); - - has_ns = true; - } - // export * as bar - // ^ - else if specifiers.is_empty() && p.input().is(&P::Token::MUL) { - has_ns = true; - } - - if has_ns { - p.assert_and_bump(&P::Token::MUL); - expect!(p, &P::Token::AS); - let name = parse_module_export_name(p)?; - specifiers.push(ExportSpecifier::Namespace(ExportNamespaceSpecifier { - span: p.span(ns_export_specifier_start), - name, - })); - } - - if has_default || has_ns { - if p.input().is(&P::Token::FROM) { - let (src, with) = parse_from_clause_and_semi(p)?; - return Ok(NamedExport { - span: p.span(start), - specifiers, - src: Some(src), - type_only, - with, - } - .into()); - } else if !p.input().syntax().export_default_from() { - // emit error - expect!(p, &P::Token::FROM); - } - - expect!(p, &P::Token::COMMA); - } - - expect!(p, &P::Token::LBRACE); - - while !p.input().is(&P::Token::RBRACE) { - let specifier = parse_named_export_specifier(p, type_only)?; - specifiers.push(ExportSpecifier::Named(specifier)); - - if p.input().is(&P::Token::RBRACE) { - break; - } else { - expect!(p, &P::Token::COMMA); - } - } - expect!(p, &P::Token::RBRACE); - - let opt = if p.input().is(&P::Token::FROM) { - Some(parse_from_clause_and_semi(p)?) - } else { - for s in &specifiers { - match s { - ExportSpecifier::Default(default) => { - p.emit_err( - default.exported.span, - SyntaxError::ExportExpectFrom(default.exported.sym.clone()), - ); - } - ExportSpecifier::Namespace(namespace) => { - let export_name = match &namespace.name { - ModuleExportName::Ident(i) => i.sym.clone(), - ModuleExportName::Str(s) => s.value.to_string_lossy().into(), - #[cfg(swc_ast_unknown)] - _ => unreachable!(), - }; - p.emit_err(namespace.span, SyntaxError::ExportExpectFrom(export_name)); - } - ExportSpecifier::Named(named) => match &named.orig { - ModuleExportName::Ident(id) if id.is_reserved() => { - p.emit_err(id.span, SyntaxError::ExportExpectFrom(id.sym.clone())); - } - ModuleExportName::Str(s) => { - p.emit_err(s.span, SyntaxError::ExportBindingIsString); - } - _ => {} - }, - #[cfg(swc_ast_unknown)] - _ => (), - } - } - - p.eat_general_semi(); - - None - }; - let (src, with) = match opt { - Some(v) => (Some(v.0), v.1), - None => (None, None), - }; - return Ok(NamedExport { - span: p.span(start), - specifiers, - src, - type_only, - with, - } - .into()); - }; - - Ok(ExportDecl { - span: p.span(start), - decl, - } - .into()) -} - -fn parse_import<'a, P: Parser<'a>>(p: &mut P) -> PResult { - let start = p.cur_pos(); - - if peek!(p).is_some_and(|cur| cur.is_dot()) { - let expr = p.parse_expr()?; - - p.eat_general_semi(); - - return Ok(ExprStmt { - span: p.span(start), - expr, - } - .into()); - } - - if peek!(p).is_some_and(|cur| cur.is_lparen()) { - let expr = p.parse_expr()?; - - p.eat_general_semi(); - - return Ok(ExprStmt { - span: p.span(start), - expr, - } - .into()); - } - - // It's now import statement - - if !p.ctx().contains(Context::Module) { - // Switch to module mode - let ctx = p.ctx() | Context::Module | Context::Strict; - p.set_ctx(ctx); - } - - expect!(p, &P::Token::IMPORT); - - // Handle import 'mod.js' - if p.input().cur().is_str() { - let src = Box::new(parse_str_lit(p)); - let with = if p.input().syntax().import_attributes() - && !p.input().had_line_break_before_cur() - && (p.input_mut().eat(&P::Token::ASSERT) || p.input_mut().eat(&P::Token::WITH)) - { - match parse_object_expr(p)? { - Expr::Object(v) => Some(Box::new(v)), - _ => unreachable!(), - } - } else { - None - }; - p.eat_general_semi(); - return Ok(ImportDecl { - span: p.span(start), - src, - specifiers: Vec::new(), - type_only: false, - with, - phase: Default::default(), - } - .into()); - } - - let mut type_only = false; - let mut phase = ImportPhase::Evaluation; - let mut specifiers = Vec::with_capacity(4); - - 'import_maybe_ident: { - if p.is_ident_ref() { - let mut local = parse_imported_default_binding(p)?; - - if p.input().syntax().typescript() && local.sym == "type" { - let cur = p.input().cur(); - if cur.is_lbrace() || cur.is_star() { - type_only = true; - break 'import_maybe_ident; - } - - if p.is_ident_ref() { - if !p.input().is(&P::Token::FROM) || peek!(p).is_some_and(|cur| cur.is_from()) { - type_only = true; - local = parse_imported_default_binding(p)?; - } else if peek!(p).is_some_and(|cur| cur.is_equal()) { - type_only = true; - local = parse_ident_name(p).map(From::from)?; - } - } - } - - if p.input().syntax().typescript() && p.input().is(&P::Token::EQUAL) { - return parse_ts_import_equals_decl(p, start, local, false, type_only) - .map(ModuleDecl::from) - .map(ModuleItem::from); - } - - if matches!(&*local.sym, "source" | "defer") { - let new_phase = match &*local.sym { - "source" => ImportPhase::Source, - "defer" => ImportPhase::Defer, - _ => unreachable!(), - }; - - let cur = p.input().cur(); - if cur.is_lbrace() || cur.is_star() { - phase = new_phase; - break 'import_maybe_ident; - } - - if p.is_ident_ref() && !p.input().is(&P::Token::FROM) - || peek!(p).is_some_and(|cur| cur.is_from()) - { - // For defer phase, we expect only namespace imports, so break here - // and let the subsequent code handle validation - if new_phase == ImportPhase::Defer { - break 'import_maybe_ident; - } - phase = new_phase; - local = parse_imported_default_binding(p)?; - } - } - - //TODO: Better error reporting - if !p.input().is(&P::Token::FROM) { - expect!(p, &P::Token::COMMA); - } - specifiers.push(ImportSpecifier::Default(ImportDefaultSpecifier { - span: local.span, - local, - })); - } - } - - { - let import_spec_start = p.cur_pos(); - // Namespace imports are not allowed in source phase. - if phase != ImportPhase::Source && p.input_mut().eat(&P::Token::MUL) { - expect!(p, &P::Token::AS); - let local = parse_imported_binding(p)?; - specifiers.push(ImportSpecifier::Namespace(ImportStarAsSpecifier { - span: p.span(import_spec_start), - local, - })); - // Named imports are only allowed in evaluation phase. - } else if phase == ImportPhase::Evaluation && p.input_mut().eat(&P::Token::LBRACE) { - while !p.input().is(&P::Token::RBRACE) { - specifiers.push(parse_import_specifier(p, type_only)?); - - if p.input().is(&P::Token::RBRACE) { - break; - } else { - expect!(p, &P::Token::COMMA); - } - } - expect!(p, &P::Token::RBRACE); - } - } - - let src = { - expect!(p, &P::Token::FROM); - if p.input().cur().is_str() { - Box::new(parse_str_lit(p)) - } else { - unexpected!(p, "a string literal") - } - }; - - let with = if p.input().syntax().import_attributes() - && !p.input().had_line_break_before_cur() - && (p.input_mut().eat(&P::Token::ASSERT) || p.input_mut().eat(&P::Token::WITH)) - { - match parse_object_expr(p)? { - Expr::Object(v) => Some(Box::new(v)), - _ => unreachable!(), - } - } else { - None - }; - - p.expect_general_semi()?; - - Ok(ImportDecl { - span: p.span(start), - specifiers, - src, - type_only, - with, - phase, - } - .into()) -} - -pub fn parse_module_item<'a>(p: &mut impl Parser<'a>) -> PResult { - p.do_inside_of_context(Context::TopLevel, |p| { - parse_stmt_like(p, true, handle_import_export) - }) -} diff --git a/crates/swc_ecma_lexer/src/common/parser/object.rs b/crates/swc_ecma_lexer/src/common/parser/object.rs deleted file mode 100644 index 1051307e5d07..000000000000 --- a/crates/swc_ecma_lexer/src/common/parser/object.rs +++ /dev/null @@ -1,444 +0,0 @@ -use swc_common::{Span, Spanned, DUMMY_SP}; -use swc_ecma_ast::*; - -use super::{ - expr::parse_assignment_expr, - pat::{parse_binding_element, parse_binding_pat_or_ident}, - PResult, Parser, -}; -use crate::{ - common::{ - context::Context, - lexer::token::TokenFactory, - parser::{ - buffer::Buffer, - class_and_fn::parse_fn_args_body, - is_not_this, - pat::{parse_formal_params, parse_unique_formal_params}, - typescript::eat_any_ts_modifier, - }, - }, - error::SyntaxError, -}; - -fn parse_object<'a, P: Parser<'a>, Object, ObjectProp>( - p: &mut P, - parse_prop: impl Fn(&mut P) -> PResult, - make_object: impl Fn(&mut P, Span, Vec, Option) -> PResult, -) -> PResult { - p.do_outside_of_context(Context::WillExpectColonForCond, |p| { - trace_cur!(p, parse_object); - - let start = p.cur_pos(); - let mut trailing_comma = None; - p.assert_and_bump(&P::Token::LBRACE); - - let mut props = Vec::with_capacity(8); - - while !p.input_mut().eat(&P::Token::RBRACE) { - props.push(parse_prop(p)?); - - if !p.input().is(&P::Token::RBRACE) { - expect!(p, &P::Token::COMMA); - if p.input().is(&P::Token::RBRACE) { - trailing_comma = Some(p.input().prev_span()); - } - } - } - - let span = p.span(start); - make_object(p, span, props, trailing_comma) - }) -} - -/// Production 'BindingProperty' -fn parse_binding_object_prop<'a, P: Parser<'a>>(p: &mut P) -> PResult { - let start = p.cur_pos(); - - if p.input_mut().eat(&P::Token::DOTDOTDOT) { - // spread element - let dot3_token = p.span(start); - - let arg = Box::new(parse_binding_pat_or_ident(p, false)?); - - return Ok(ObjectPatProp::Rest(RestPat { - span: p.span(start), - dot3_token, - arg, - type_ann: None, - })); - } - - let key = p.parse_prop_name()?; - if p.input_mut().eat(&P::Token::COLON) { - let value = Box::new(parse_binding_element(p)?); - - return Ok(ObjectPatProp::KeyValue(KeyValuePatProp { key, value })); - } - let key = match key { - PropName::Ident(ident) => ident, - _ => unexpected!(p, "an identifier"), - }; - - let value = if p.input_mut().eat(&P::Token::EQUAL) { - p.allow_in_expr(parse_assignment_expr).map(Some)? - } else { - if p.ctx().is_reserved_word(&key.sym) { - p.emit_err(key.span, SyntaxError::ReservedWordInObjShorthandOrPat); - } - - None - }; - - Ok(ObjectPatProp::Assign(AssignPatProp { - span: p.span(start), - key: key.into(), - value, - })) -} - -fn make_binding_object<'a, P: Parser<'a>>( - p: &mut P, - span: Span, - props: Vec, - trailing_comma: Option, -) -> PResult { - let len = props.len(); - for (i, prop) in props.iter().enumerate() { - if i == len - 1 { - if let ObjectPatProp::Rest(ref rest) = prop { - match *rest.arg { - Pat::Ident(..) => { - if let Some(trailing_comma) = trailing_comma { - p.emit_err(trailing_comma, SyntaxError::CommaAfterRestElement); - } - } - _ => syntax_error!(p, prop.span(), SyntaxError::DotsWithoutIdentifier), - } - } - continue; - } - - if let ObjectPatProp::Rest(..) = prop { - p.emit_err(prop.span(), SyntaxError::NonLastRestParam) - } - } - - let optional = (p.input().syntax().dts() || p.ctx().contains(Context::InDeclare)) - && p.input_mut().eat(&P::Token::QUESTION); - - Ok(ObjectPat { - span, - props, - optional, - type_ann: None, - } - .into()) -} - -pub(super) fn parse_object_pat<'a, P: Parser<'a>>(p: &mut P) -> PResult { - parse_object(p, parse_binding_object_prop, make_binding_object) -} - -fn make_expr_object<'a, P: Parser<'a>>( - p: &mut P, - span: Span, - props: Vec, - trailing_comma: Option, -) -> PResult { - if let Some(trailing_comma) = trailing_comma { - p.state_mut() - .trailing_commas - .insert(span.lo, trailing_comma); - } - Ok(ObjectLit { span, props }.into()) -} - -fn parse_expr_object_prop<'a, P: Parser<'a>>(p: &mut P) -> PResult { - trace_cur!(p, parse_object_prop); - - let start = p.cur_pos(); - // Parse as 'MethodDefinition' - - if p.input_mut().eat(&P::Token::DOTDOTDOT) { - // spread element - let dot3_token = p.span(start); - - let expr = p.allow_in_expr(parse_assignment_expr)?; - - return Ok(PropOrSpread::Spread(SpreadElement { dot3_token, expr })); - } - - if p.input_mut().eat(&P::Token::MUL) { - let name = p.parse_prop_name()?; - return p - .do_inside_of_context(Context::AllowDirectSuper, |p| { - p.do_outside_of_context(Context::InClassField, |p| { - parse_fn_args_body( - p, - // no decorator in an object literal - Vec::new(), - start, - parse_unique_formal_params, - false, - true, - ) - }) - }) - .map(|function| { - PropOrSpread::Prop(Box::new(Prop::Method(MethodProp { - key: name, - function, - }))) - }); - } - - let has_modifiers = eat_any_ts_modifier(p)?; - let modifiers_span = p.input().prev_span(); - - let key = p.parse_prop_name()?; - - let cur = p.input().cur(); - if p.input().syntax().typescript() - && !(cur.is_lparen() - || cur.is_lbracket() - || cur.is_colon() - || cur.is_comma() - || cur.is_question() - || cur.is_equal() - || cur.is_star() - || cur.is_str() - || cur.is_num() - || cur.is_word()) - && !(p.input().syntax().typescript() && p.input().is(&P::Token::LESS)) - && !(p.input().is(&P::Token::RBRACE) && matches!(key, PropName::Ident(..))) - { - trace_cur!(p, parse_object_prop_error); - - p.emit_err(p.input().cur_span(), SyntaxError::TS1005); - return Ok(PropOrSpread::Prop(Box::new(Prop::KeyValue(KeyValueProp { - key, - value: Invalid { - span: p.span(start), - } - .into(), - })))); - } - // - // {[computed()]: a,} - // { 'a': a, } - // { 0: 1, } - // { a: expr, } - if p.input_mut().eat(&P::Token::COLON) { - let value = p.allow_in_expr(parse_assignment_expr)?; - return Ok(PropOrSpread::Prop(Box::new(Prop::KeyValue(KeyValueProp { - key, - value, - })))); - } - - // Handle `a(){}` (and async(){} / get(){} / set(){}) - if (p.input().syntax().typescript() && p.input().is(&P::Token::LESS)) - || p.input().is(&P::Token::LPAREN) - { - return p - .do_inside_of_context(Context::AllowDirectSuper, |p| { - p.do_outside_of_context(Context::InClassField, |p| { - parse_fn_args_body( - p, - // no decorator in an object literal - Vec::new(), - start, - parse_unique_formal_params, - false, - false, - ) - }) - }) - .map(|function| Box::new(Prop::Method(MethodProp { key, function }))) - .map(PropOrSpread::Prop); - } - - let ident = match key { - PropName::Ident(ident) => ident, - // TODO - _ => unexpected!(p, "identifier"), - }; - - if p.input_mut().eat(&P::Token::QUESTION) { - p.emit_err(p.input().prev_span(), SyntaxError::TS1162); - } - - // `ident` from parse_prop_name is parsed as 'IdentifierName' - // It means we should check for invalid expressions like { for, } - let cur = p.input().cur(); - if cur.is_equal() || cur.is_comma() || cur.is_rbrace() { - if p.ctx().is_reserved_word(&ident.sym) { - p.emit_err(ident.span, SyntaxError::ReservedWordInObjShorthandOrPat); - } - - if p.input_mut().eat(&P::Token::EQUAL) { - let value = p.allow_in_expr(parse_assignment_expr)?; - let span = p.span(start); - return Ok(PropOrSpread::Prop(Box::new(Prop::Assign(AssignProp { - span, - key: ident.into(), - value, - })))); - } - - return Ok(PropOrSpread::Prop(Box::new(Prop::from(ident)))); - } - - // get a(){} - // set a(v){} - // async a(){} - - match &*ident.sym { - "get" | "set" | "async" => { - trace_cur!(p, parse_object_prop__after_accessor); - - if has_modifiers { - p.emit_err(modifiers_span, SyntaxError::TS1042); - } - - let is_generator = ident.sym == "async" && p.input_mut().eat(&P::Token::MUL); - let key = p.parse_prop_name()?; - let key_span = key.span(); - p.do_inside_of_context(Context::AllowDirectSuper, |p| { - p.do_outside_of_context(Context::InClassField, |parser| { - match &*ident.sym { - "get" => parse_fn_args_body( - parser, - // no decorator in an object literal - Vec::new(), - start, - |p| { - let params = parse_formal_params(p)?; - - if params.iter().any(is_not_this) { - p.emit_err(key_span, SyntaxError::GetterParam); - } - - Ok(params) - }, - false, - false, - ) - .map(|v| *v) - .map( - |Function { - body, return_type, .. - }| { - if parser.input().syntax().typescript() - && parser.input().target() == EsVersion::Es3 - { - parser.emit_err(key_span, SyntaxError::TS1056); - } - - PropOrSpread::Prop(Box::new(Prop::Getter(GetterProp { - span: parser.span(start), - key, - type_ann: return_type, - body, - }))) - }, - ), - "set" => { - parse_fn_args_body( - parser, - // no decorator in an object literal - Vec::new(), - start, - |p| { - let params = parse_formal_params(p)?; - - if params.iter().filter(|p| is_not_this(p)).count() != 1 { - p.emit_err(key_span, SyntaxError::SetterParam); - } - - if !params.is_empty() { - if let Pat::Rest(..) = params[0].pat { - p.emit_err( - params[0].span(), - SyntaxError::RestPatInSetter, - ); - } - } - - if p.input().syntax().typescript() - && p.input().target() == EsVersion::Es3 - { - p.emit_err(key_span, SyntaxError::TS1056); - } - - Ok(params) - }, - false, - false, - ) - .map(|v| *v) - .map( - |Function { - mut params, body, .. - }| { - let mut this = None; - if params.len() >= 2 { - this = Some(params.remove(0).pat); - } - - let param = Box::new( - params.into_iter().next().map(|v| v.pat).unwrap_or_else( - || { - parser.emit_err(key_span, SyntaxError::SetterParam); - - Invalid { span: DUMMY_SP }.into() - }, - ), - ); - - // debug_assert_eq!(params.len(), 1); - PropOrSpread::Prop(Box::new(Prop::Setter(SetterProp { - span: parser.span(start), - key, - body, - param, - this_param: this, - }))) - }, - ) - } - "async" => parse_fn_args_body( - parser, - // no decorator in an object literal - Vec::new(), - start, - parse_unique_formal_params, - true, - is_generator, - ) - .map(|function| { - PropOrSpread::Prop(Box::new(Prop::Method(MethodProp { key, function }))) - }), - _ => unreachable!(), - } - }) - }) - } - _ => { - if p.input().syntax().typescript() { - unexpected!( - p, - "... , *, (, [, :, , ?, =, an identifier, public, protected, private, \ - readonly, <." - ) - } else { - unexpected!(p, "... , *, (, [, :, , ?, = or an identifier") - } - } - } -} - -pub fn parse_object_expr<'a, P: Parser<'a>>(p: &mut P) -> PResult { - parse_object(p, parse_expr_object_prop, make_expr_object) -} diff --git a/crates/swc_ecma_lexer/src/common/parser/output_type.rs b/crates/swc_ecma_lexer/src/common/parser/output_type.rs deleted file mode 100644 index a300ca511611..000000000000 --- a/crates/swc_ecma_lexer/src/common/parser/output_type.rs +++ /dev/null @@ -1,113 +0,0 @@ -use swc_common::Span; -use swc_ecma_ast::{ - Class, ClassDecl, ClassExpr, Decl, DefaultDecl, ExportDefaultDecl, Expr, FnDecl, FnExpr, - Function, Ident, -}; - -use crate::error::SyntaxError; - -pub trait OutputType: Sized { - const IS_IDENT_REQUIRED: bool; - - /// From babel.. - /// - /// When parsing function expression, the binding identifier is parsed - /// according to the rules inside the function. - /// e.g. (function* yield() {}) is invalid because "yield" is disallowed in - /// generators. - /// This isn't the case with function declarations: function* yield() {} is - /// valid because yield is parsed as if it was outside the generator. - /// Therefore, this.state.inGenerator is set before or after parsing the - /// function id according to the "isStatement" parameter. - fn is_fn_expr() -> bool { - false - } - - fn finish_fn(span: Span, ident: Option, f: Box) -> Result; - - fn finish_class( - span: Span, - ident: Option, - class: Box, - ) -> Result; -} - -impl OutputType for Box { - const IS_IDENT_REQUIRED: bool = false; - - fn is_fn_expr() -> bool { - true - } - - fn finish_fn( - _span: Span, - ident: Option, - function: Box, - ) -> Result { - Ok(FnExpr { ident, function }.into()) - } - - fn finish_class( - _span: Span, - ident: Option, - class: Box, - ) -> Result { - Ok(ClassExpr { ident, class }.into()) - } -} - -impl OutputType for ExportDefaultDecl { - const IS_IDENT_REQUIRED: bool = false; - - fn finish_fn( - span: Span, - ident: Option, - function: Box, - ) -> Result { - Ok(ExportDefaultDecl { - span, - decl: DefaultDecl::Fn(FnExpr { ident, function }), - }) - } - - fn finish_class( - span: Span, - ident: Option, - class: Box, - ) -> Result { - Ok(ExportDefaultDecl { - span, - decl: DefaultDecl::Class(ClassExpr { ident, class }), - }) - } -} - -impl OutputType for Decl { - const IS_IDENT_REQUIRED: bool = true; - - fn finish_fn( - _span: Span, - ident: Option, - function: Box, - ) -> Result { - let ident = ident.ok_or(SyntaxError::ExpectedIdent)?; - - Ok(FnDecl { - declare: false, - ident, - function, - } - .into()) - } - - fn finish_class(_: Span, ident: Option, class: Box) -> Result { - let ident = ident.ok_or(SyntaxError::ExpectedIdent)?; - - Ok(ClassDecl { - declare: false, - ident, - class, - } - .into()) - } -} diff --git a/crates/swc_ecma_lexer/src/common/parser/pat.rs b/crates/swc_ecma_lexer/src/common/parser/pat.rs deleted file mode 100644 index 66d97a3538c4..000000000000 --- a/crates/swc_ecma_lexer/src/common/parser/pat.rs +++ /dev/null @@ -1,838 +0,0 @@ -use swc_common::{BytePos, Span, Spanned}; -use swc_ecma_ast::*; - -use super::{ - assign_target_or_spread::AssignTargetOrSpread, - class_and_fn::{parse_access_modifier, parse_decorators}, - pat_type::PatType, - typescript::{ - eat_any_ts_modifier, parse_ts_modifier, parse_ts_type_ann, try_parse_ts_type_ann, - }, - PResult, Parser, -}; -use crate::{ - common::{ - context::Context, - lexer::token::TokenFactory, - parser::{ - buffer::Buffer, expr::parse_assignment_expr, expr_ext::ExprExt, - ident::parse_binding_ident, object::parse_object_pat, - }, - }, - error::SyntaxError, -}; - -/// argument of arrow is pattern, although idents in pattern is already -/// checked if is a keyword, it should also be checked if is arguments or -/// eval -fn pat_is_valid_argument_in_strict<'a>(p: &mut impl Parser<'a>, pat: &Pat) { - debug_assert!(p.ctx().contains(Context::Strict)); - match pat { - Pat::Ident(i) => { - if i.is_reserved_in_strict_bind() { - p.emit_strict_mode_err(i.span, SyntaxError::EvalAndArgumentsInStrict) - } - } - Pat::Array(arr) => { - for pat in arr.elems.iter().flatten() { - pat_is_valid_argument_in_strict(p, pat) - } - } - Pat::Rest(r) => pat_is_valid_argument_in_strict(p, &r.arg), - Pat::Object(obj) => { - for prop in obj.props.iter() { - match prop { - ObjectPatProp::KeyValue(KeyValuePatProp { value, .. }) - | ObjectPatProp::Rest(RestPat { arg: value, .. }) => { - pat_is_valid_argument_in_strict(p, value) - } - ObjectPatProp::Assign(AssignPatProp { key, .. }) => { - if key.is_reserved_in_strict_bind() { - p.emit_strict_mode_err(key.span, SyntaxError::EvalAndArgumentsInStrict) - } - } - #[cfg(swc_ast_unknown)] - _ => (), - } - } - } - Pat::Assign(a) => pat_is_valid_argument_in_strict(p, &a.left), - Pat::Invalid(_) | Pat::Expr(_) => (), - #[cfg(swc_ast_unknown)] - _ => (), - } -} - -/// This does not return 'rest' pattern because non-last parameter cannot be -/// rest. -pub(super) fn reparse_expr_as_pat<'a>( - p: &mut impl Parser<'a>, - pat_ty: PatType, - expr: Box, -) -> PResult { - if let Expr::Invalid(i) = *expr { - return Ok(i.into()); - } - if pat_ty == PatType::AssignPat { - match *expr { - Expr::Object(..) | Expr::Array(..) => { - // It is a Syntax Error if LeftHandSideExpression is either - // an ObjectLiteral or an ArrayLiteral - // and LeftHandSideExpression cannot - // be reparsed as an AssignmentPattern. - } - _ => { - p.check_assign_target(&expr, true); - } - } - } - reparse_expr_as_pat_inner(p, pat_ty, expr) -} - -fn reparse_expr_as_pat_inner<'a>( - p: &mut impl Parser<'a>, - pat_ty: PatType, - expr: Box, -) -> PResult { - // In dts, we do not reparse. - debug_assert!(!p.input().syntax().dts()); - let span = expr.span(); - if pat_ty == PatType::AssignPat { - match *expr { - Expr::Object(..) | Expr::Array(..) => { - // It is a Syntax Error if LeftHandSideExpression is either - // an ObjectLiteral or an ArrayLiteral - // and LeftHandSideExpression cannot - // be reparsed as an AssignmentPattern. - } - - _ => match *expr { - // It is a Syntax Error if the LeftHandSideExpression is - // CoverParenthesizedExpressionAndArrowParameterList:(Expression) and - // Expression derives a phrase that would produce a Syntax Error according - // to these rules if that phrase were substituted for - // LeftHandSideExpression. This rule is recursively applied. - Expr::Paren(..) => { - return Ok(expr.into()); - } - Expr::Ident(i) => return Ok(i.into()), - _ => { - return Ok(expr.into()); - } - }, - } - } - - // AssignmentElement: - // DestructuringAssignmentTarget Initializer[+In]? - // - // DestructuringAssignmentTarget: - // LeftHandSideExpression - if pat_ty == PatType::AssignElement { - match *expr { - Expr::Array(..) | Expr::Object(..) => {} - Expr::Member(..) - | Expr::SuperProp(..) - | Expr::Call(..) - | Expr::New(..) - | Expr::Lit(..) - | Expr::Ident(..) - | Expr::Fn(..) - | Expr::Class(..) - | Expr::Paren(..) - | Expr::Tpl(..) - | Expr::TsAs(..) => { - if !expr.is_valid_simple_assignment_target(p.ctx().contains(Context::Strict)) { - p.emit_err(span, SyntaxError::NotSimpleAssign) - } - match *expr { - Expr::Ident(i) => return Ok(i.into()), - _ => { - return Ok(expr.into()); - } - } - } - // It's special because of optional initializer - Expr::Assign(..) => {} - _ => p.emit_err(span, SyntaxError::InvalidPat), - } - } - - match *expr { - Expr::Paren(..) => { - p.emit_err(span, SyntaxError::InvalidPat); - Ok(Invalid { span }.into()) - } - Expr::Assign( - assign_expr @ AssignExpr { - op: AssignOp::Assign, - .. - }, - ) => { - let AssignExpr { - span, left, right, .. - } = assign_expr; - Ok(AssignPat { - span, - left: match left { - AssignTarget::Simple(left) => { - Box::new(reparse_expr_as_pat(p, pat_ty, left.into())?) - } - AssignTarget::Pat(pat) => pat.into(), - #[cfg(swc_ast_unknown)] - _ => unreachable!(), - }, - right, - } - .into()) - } - Expr::Object(ObjectLit { - span: object_span, - props, - }) => { - // {} - let len = props.len(); - Ok(ObjectPat { - span: object_span, - props: props - .into_iter() - .enumerate() - .map(|(idx, prop)| { - let span = prop.span(); - match prop { - PropOrSpread::Prop(prop) => match *prop { - Prop::Shorthand(id) => Ok(ObjectPatProp::Assign(AssignPatProp { - span: id.span(), - key: id.into(), - value: None, - })), - Prop::KeyValue(kv_prop) => { - Ok(ObjectPatProp::KeyValue(KeyValuePatProp { - key: kv_prop.key, - value: Box::new(reparse_expr_as_pat( - p, - pat_ty.element(), - kv_prop.value, - )?), - })) - } - Prop::Assign(assign_prop) => { - Ok(ObjectPatProp::Assign(AssignPatProp { - span, - key: assign_prop.key.into(), - value: Some(assign_prop.value), - })) - } - _ => syntax_error!(p, prop.span(), SyntaxError::InvalidPat), - }, - - PropOrSpread::Spread(SpreadElement { dot3_token, expr }) => { - if idx != len - 1 { - p.emit_err(span, SyntaxError::NonLastRestParam) - } else if let Some(trailing_comma) = - p.state().trailing_commas.get(&object_span.lo) - { - p.emit_err(*trailing_comma, SyntaxError::CommaAfterRestElement); - }; - - let element_pat_ty = pat_ty.element(); - let pat = if let PatType::BindingElement = element_pat_ty { - if let Expr::Ident(i) = *expr { - i.into() - } else { - p.emit_err(span, SyntaxError::DotsWithoutIdentifier); - Pat::Invalid(Invalid { span }) - } - } else { - reparse_expr_as_pat(p, element_pat_ty, expr)? - }; - if let Pat::Assign(_) = pat { - p.emit_err(span, SyntaxError::TS1048) - }; - Ok(ObjectPatProp::Rest(RestPat { - span, - dot3_token, - arg: Box::new(pat), - type_ann: None, - })) - } - - #[cfg(swc_ast_unknown)] - _ => unreachable!(), - } - }) - .collect::>()?, - optional: false, - type_ann: None, - } - .into()) - } - Expr::Ident(ident) => Ok(ident.into()), - Expr::Array(ArrayLit { - elems: mut exprs, .. - }) => { - if exprs.is_empty() { - return Ok(ArrayPat { - span, - elems: Vec::new(), - optional: false, - type_ann: None, - } - .into()); - } - // Trailing comma may exist. We should remove those commas. - let count_of_trailing_comma = exprs.iter().rev().take_while(|e| e.is_none()).count(); - let len = exprs.len(); - let mut params = Vec::with_capacity(exprs.len() - count_of_trailing_comma); - // Comma or other pattern cannot follow a rest pattern. - let idx_of_rest_not_allowed = if count_of_trailing_comma == 0 { - len - 1 - } else { - // last element is comma, so rest is not allowed for every pattern element. - len - count_of_trailing_comma - }; - for expr in exprs.drain(..idx_of_rest_not_allowed) { - match expr { - Some( - expr @ ExprOrSpread { - spread: Some(..), .. - }, - ) => p.emit_err(expr.span(), SyntaxError::NonLastRestParam), - Some(ExprOrSpread { expr, .. }) => { - params.push(reparse_expr_as_pat(p, pat_ty.element(), expr).map(Some)?) - } - None => params.push(None), - } - } - if count_of_trailing_comma == 0 { - let expr = exprs.into_iter().next().unwrap(); - let outer_expr_span = expr.span(); - let last = match expr { - // Rest - Some(ExprOrSpread { - spread: Some(dot3_token), - expr, - }) => { - // TODO: is BindingPat correct? - if let Expr::Assign(_) = *expr { - p.emit_err(outer_expr_span, SyntaxError::TS1048); - }; - if let Some(trailing_comma) = p.state().trailing_commas.get(&span.lo) { - p.emit_err(*trailing_comma, SyntaxError::CommaAfterRestElement); - } - let expr_span = expr.span(); - reparse_expr_as_pat(p, pat_ty.element(), expr) - .map(|pat| { - RestPat { - span: expr_span, - dot3_token, - arg: Box::new(pat), - type_ann: None, - } - .into() - }) - .map(Some)? - } - Some(ExprOrSpread { expr, .. }) => { - // TODO: is BindingPat correct? - reparse_expr_as_pat(p, pat_ty.element(), expr).map(Some)? - } - // TODO: syntax error if last element is ellison and ...rest exists. - None => None, - }; - params.push(last); - } - Ok(ArrayPat { - span, - elems: params, - optional: false, - type_ann: None, - } - .into()) - } - - // Invalid patterns. - // Note that assignment expression with '=' is valid, and handled above. - Expr::Lit(..) | Expr::Assign(..) => { - p.emit_err(span, SyntaxError::InvalidPat); - Ok(Invalid { span }.into()) - } - - Expr::Yield(..) if p.ctx().contains(Context::InGenerator) => { - p.emit_err(span, SyntaxError::InvalidPat); - Ok(Invalid { span }.into()) - } - - _ => { - p.emit_err(span, SyntaxError::InvalidPat); - - Ok(Invalid { span }.into()) - } - } -} - -pub(super) fn parse_binding_element<'a, P: Parser<'a>>(p: &mut P) -> PResult { - trace_cur!(p, parse_binding_element); - - let start = p.cur_pos(); - let left = parse_binding_pat_or_ident(p, false)?; - - if p.input_mut().eat(&P::Token::EQUAL) { - let right = p.allow_in_expr(parse_assignment_expr)?; - - if p.ctx().contains(Context::InDeclare) { - p.emit_err(p.span(start), SyntaxError::TS2371); - } - - return Ok(AssignPat { - span: p.span(start), - left: Box::new(left), - right, - } - .into()); - } - - Ok(left) -} - -pub fn parse_binding_pat_or_ident<'a, P: Parser<'a>>( - p: &mut P, - disallow_let: bool, -) -> PResult { - trace_cur!(p, parse_binding_pat_or_ident); - - let cur = p.input().cur(); - if cur.is_word() { - parse_binding_ident(p, disallow_let).map(Pat::from) - } else if cur.is_lbracket() { - parse_array_binding_pat(p) - } else if cur.is_lbrace() { - parse_object_pat(p) - } else if cur.is_error() { - let err = p.input_mut().expect_error_token_and_bump(); - Err(err) - } else { - unexpected!(p, "yield, an identifier, [ or {") - } -} - -pub fn parse_array_binding_pat<'a, P: Parser<'a>>(p: &mut P) -> PResult { - let start = p.cur_pos(); - - p.assert_and_bump(&P::Token::LBRACKET); - - let mut elems = Vec::new(); - - let mut rest_span = Span::default(); - - while !p.input().is(&P::Token::RBRACKET) { - if p.input_mut().eat(&P::Token::COMMA) { - elems.push(None); - continue; - } - - if !rest_span.is_dummy() { - p.emit_err(rest_span, SyntaxError::NonLastRestParam); - } - - let start = p.cur_pos(); - - let mut is_rest = false; - if p.input_mut().eat(&P::Token::DOTDOTDOT) { - is_rest = true; - let dot3_token = p.span(start); - - let pat = parse_binding_pat_or_ident(p, false)?; - rest_span = p.span(start); - let pat = RestPat { - span: rest_span, - dot3_token, - arg: Box::new(pat), - type_ann: None, - } - .into(); - elems.push(Some(pat)); - } else { - elems.push(parse_binding_element(p).map(Some)?); - } - - if !p.input().is(&P::Token::RBRACKET) { - expect!(p, &P::Token::COMMA); - if is_rest && p.input().is(&P::Token::RBRACKET) { - p.emit_err(p.input().prev_span(), SyntaxError::CommaAfterRestElement); - } - } - } - - expect!(p, &P::Token::RBRACKET); - let optional = (p.input().syntax().dts() || p.ctx().contains(Context::InDeclare)) - && p.input_mut().eat(&P::Token::QUESTION); - - Ok(ArrayPat { - span: p.span(start), - elems, - optional, - type_ann: None, - } - .into()) -} - -/// spec: 'FormalParameter' -/// -/// babel: `parseAssignableListItem` -fn parse_formal_param_pat<'a, P: Parser<'a>>(p: &mut P) -> PResult { - let start = p.cur_pos(); - - let has_modifier = eat_any_ts_modifier(p)?; - - let pat_start = p.cur_pos(); - let mut pat = parse_binding_element(p)?; - let mut opt = false; - - if p.input().syntax().typescript() { - if p.input_mut().eat(&P::Token::QUESTION) { - match pat { - Pat::Ident(BindingIdent { - id: Ident { - ref mut optional, .. - }, - .. - }) - | Pat::Array(ArrayPat { - ref mut optional, .. - }) - | Pat::Object(ObjectPat { - ref mut optional, .. - }) => { - *optional = true; - opt = true; - } - _ if p.input().syntax().dts() || p.ctx().contains(Context::InDeclare) => {} - _ => { - syntax_error!( - p, - p.input().prev_span(), - SyntaxError::TsBindingPatCannotBeOptional - ); - } - } - } - - match pat { - Pat::Array(ArrayPat { - ref mut type_ann, - ref mut span, - .. - }) - | Pat::Object(ObjectPat { - ref mut type_ann, - ref mut span, - .. - }) - | Pat::Rest(RestPat { - ref mut type_ann, - ref mut span, - .. - }) => { - let new_type_ann = try_parse_ts_type_ann(p)?; - if new_type_ann.is_some() { - *span = Span::new_with_checked(pat_start, p.input().prev_span().hi); - } - *type_ann = new_type_ann; - } - - Pat::Ident(BindingIdent { - ref mut type_ann, .. - }) => { - let new_type_ann = try_parse_ts_type_ann(p)?; - *type_ann = new_type_ann; - } - - Pat::Assign(AssignPat { ref mut span, .. }) => { - if (try_parse_ts_type_ann(p)?).is_some() { - *span = Span::new_with_checked(pat_start, p.input().prev_span().hi); - p.emit_err(*span, SyntaxError::TSTypeAnnotationAfterAssign); - } - } - Pat::Invalid(..) => {} - _ => unreachable!("invalid syntax: Pat: {:?}", pat), - } - } - - let pat = if p.input_mut().eat(&P::Token::EQUAL) { - // `=` cannot follow optional parameter. - if opt { - p.emit_err(pat.span(), SyntaxError::TS1015); - } - - let right = parse_assignment_expr(p)?; - if p.ctx().contains(Context::InDeclare) { - p.emit_err(p.span(start), SyntaxError::TS2371); - } - - AssignPat { - span: p.span(start), - left: Box::new(pat), - right, - } - .into() - } else { - pat - }; - - if has_modifier { - p.emit_err(p.span(start), SyntaxError::TS2369); - return Ok(pat); - } - - Ok(pat) -} - -fn parse_constructor_param<'a, P: Parser<'a>>( - p: &mut P, - param_start: BytePos, - decorators: Vec, -) -> PResult { - let (accessibility, is_override, readonly) = if p.input().syntax().typescript() { - let accessibility = parse_access_modifier(p)?; - ( - accessibility, - parse_ts_modifier(p, &["override"], false)?.is_some(), - parse_ts_modifier(p, &["readonly"], false)?.is_some(), - ) - } else { - (None, false, false) - }; - if accessibility.is_none() && !is_override && !readonly { - let pat = parse_formal_param_pat(p)?; - Ok(ParamOrTsParamProp::Param(Param { - span: p.span(param_start), - decorators, - pat, - })) - } else { - let param = match parse_formal_param_pat(p)? { - Pat::Ident(i) => TsParamPropParam::Ident(i), - Pat::Assign(a) => TsParamPropParam::Assign(a), - node => syntax_error!(p, node.span(), SyntaxError::TsInvalidParamPropPat), - }; - Ok(ParamOrTsParamProp::TsParamProp(TsParamProp { - span: p.span(param_start), - accessibility, - is_override, - readonly, - decorators, - param, - })) - } -} - -pub fn parse_constructor_params<'a, P: Parser<'a>>(p: &mut P) -> PResult> { - let mut params = Vec::new(); - let mut rest_span = Span::default(); - - while !p.input().is(&P::Token::RPAREN) { - if !rest_span.is_dummy() { - p.emit_err(rest_span, SyntaxError::TS1014); - } - - let param_start = p.cur_pos(); - let decorators = parse_decorators(p, false)?; - let pat_start = p.cur_pos(); - - let mut is_rest = false; - if p.input_mut().eat(&P::Token::DOTDOTDOT) { - is_rest = true; - let dot3_token = p.span(pat_start); - - let pat = parse_binding_pat_or_ident(p, false)?; - let type_ann = if p.input().syntax().typescript() && p.input().is(&P::Token::COLON) { - let cur_pos = p.cur_pos(); - Some(parse_ts_type_ann(p, /* eat_colon */ true, cur_pos)?) - } else { - None - }; - - rest_span = p.span(pat_start); - let pat = RestPat { - span: rest_span, - dot3_token, - arg: Box::new(pat), - type_ann, - } - .into(); - params.push(ParamOrTsParamProp::Param(Param { - span: p.span(param_start), - decorators, - pat, - })); - } else { - params.push(parse_constructor_param(p, param_start, decorators)?); - } - - if !p.input().is(&P::Token::RPAREN) { - expect!(p, &P::Token::COMMA); - if p.input().is(&P::Token::RPAREN) && is_rest { - p.emit_err(p.input().prev_span(), SyntaxError::CommaAfterRestElement); - } - } - } - - Ok(params) -} - -pub fn parse_formal_params<'a, P: Parser<'a>>(p: &mut P) -> PResult> { - let mut params = Vec::new(); - let mut rest_span = Span::default(); - - while !p.input().is(&P::Token::RPAREN) { - if !rest_span.is_dummy() { - p.emit_err(rest_span, SyntaxError::TS1014); - } - - let param_start = p.cur_pos(); - let decorators = parse_decorators(p, false)?; - let pat_start = p.cur_pos(); - - let pat = if p.input_mut().eat(&P::Token::DOTDOTDOT) { - let dot3_token = p.span(pat_start); - - let mut pat = parse_binding_pat_or_ident(p, false)?; - - if p.input_mut().eat(&P::Token::EQUAL) { - let right = parse_assignment_expr(p)?; - p.emit_err(pat.span(), SyntaxError::TS1048); - pat = AssignPat { - span: p.span(pat_start), - left: Box::new(pat), - right, - } - .into(); - } - - let type_ann = if p.input().syntax().typescript() && p.input().is(&P::Token::COLON) { - let cur_pos = p.cur_pos(); - let ty = parse_ts_type_ann(p, /* eat_colon */ true, cur_pos)?; - Some(ty) - } else { - None - }; - - rest_span = p.span(pat_start); - let pat = RestPat { - span: rest_span, - dot3_token, - arg: Box::new(pat), - type_ann, - } - .into(); - - if p.syntax().typescript() && p.input_mut().eat(&P::Token::QUESTION) { - p.emit_err(p.input().prev_span(), SyntaxError::TS1047); - // - } - - pat - } else { - parse_formal_param_pat(p)? - }; - let is_rest = matches!(pat, Pat::Rest(_)); - - params.push(Param { - span: p.span(param_start), - decorators, - pat, - }); - - if !p.input().is(&P::Token::RPAREN) { - expect!(p, &P::Token::COMMA); - if is_rest && p.input().is(&P::Token::RPAREN) { - p.emit_err(p.input().prev_span(), SyntaxError::CommaAfterRestElement); - } - } - } - - Ok(params) -} - -pub fn parse_unique_formal_params<'a>(p: &mut impl Parser<'a>) -> PResult> { - // FIXME: This is wrong - parse_formal_params(p) -} - -pub(super) fn parse_paren_items_as_params<'a, P: Parser<'a>>( - p: &mut P, - mut exprs: Vec, - trailing_comma: Option, -) -> PResult> { - let pat_ty = PatType::BindingPat; - - let len = exprs.len(); - if len == 0 { - return Ok(Vec::new()); - } - - let mut params = Vec::with_capacity(len); - - for expr in exprs.drain(..len - 1) { - match expr { - AssignTargetOrSpread::ExprOrSpread(ExprOrSpread { - spread: Some(..), .. - }) - | AssignTargetOrSpread::Pat(Pat::Rest(..)) => { - p.emit_err(expr.span(), SyntaxError::TS1014) - } - AssignTargetOrSpread::ExprOrSpread(ExprOrSpread { - spread: None, expr, .. - }) => params.push(reparse_expr_as_pat(p, pat_ty, expr)?), - AssignTargetOrSpread::Pat(pat) => params.push(pat), - } - } - - debug_assert_eq!(exprs.len(), 1); - let expr = exprs.pop().unwrap(); - let outer_expr_span = expr.span(); - let last = match expr { - // Rest - AssignTargetOrSpread::ExprOrSpread(ExprOrSpread { - spread: Some(dot3_token), - expr, - }) => { - if let Expr::Assign(_) = *expr { - p.emit_err(outer_expr_span, SyntaxError::TS1048) - }; - if let Some(trailing_comma) = trailing_comma { - p.emit_err(trailing_comma, SyntaxError::CommaAfterRestElement); - } - let expr_span = expr.span(); - reparse_expr_as_pat(p, pat_ty, expr).map(|pat| { - RestPat { - span: expr_span, - dot3_token, - arg: Box::new(pat), - type_ann: None, - } - .into() - })? - } - AssignTargetOrSpread::ExprOrSpread(ExprOrSpread { expr, .. }) => { - reparse_expr_as_pat(p, pat_ty, expr)? - } - AssignTargetOrSpread::Pat(pat) => { - if let Some(trailing_comma) = trailing_comma { - if let Pat::Rest(..) = pat { - p.emit_err(trailing_comma, SyntaxError::CommaAfterRestElement); - } - } - pat - } - }; - params.push(last); - - if p.ctx().contains(Context::Strict) { - for param in params.iter() { - pat_is_valid_argument_in_strict(p, param) - } - } - Ok(params) -} diff --git a/crates/swc_ecma_lexer/src/common/parser/pat_type.rs b/crates/swc_ecma_lexer/src/common/parser/pat_type.rs deleted file mode 100644 index 705a63b2ec9e..000000000000 --- a/crates/swc_ecma_lexer/src/common/parser/pat_type.rs +++ /dev/null @@ -1,17 +0,0 @@ -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub(super) enum PatType { - BindingPat, - BindingElement, - /// AssignmentPattern - AssignPat, - AssignElement, -} - -impl PatType { - pub fn element(self) -> Self { - match self { - PatType::BindingPat | PatType::BindingElement => PatType::BindingElement, - PatType::AssignPat | PatType::AssignElement => PatType::AssignElement, - } - } -} diff --git a/crates/swc_ecma_lexer/src/common/parser/state.rs b/crates/swc_ecma_lexer/src/common/parser/state.rs deleted file mode 100644 index 07a66fbe4411..000000000000 --- a/crates/swc_ecma_lexer/src/common/parser/state.rs +++ /dev/null @@ -1,39 +0,0 @@ -use std::ops::{Deref, DerefMut}; - -use rustc_hash::FxHashMap; -use swc_atoms::Atom; -use swc_common::{BytePos, Span}; - -#[derive(Clone, Default)] -pub struct State { - pub labels: Vec, - /// Start position of an assignment expression. - pub potential_arrow_start: Option, - /// Start position of an AST node and the span of its trailing comma. - pub trailing_commas: FxHashMap, -} - -pub struct WithState<'a, 'w, Parser: super::Parser<'a>> { - pub(super) inner: &'w mut Parser, - pub(super) orig_state: crate::common::parser::state::State, - pub(super) marker: std::marker::PhantomData<&'a ()>, -} - -impl<'a, Parser: super::Parser<'a>> Deref for WithState<'a, '_, Parser> { - type Target = Parser; - - fn deref(&self) -> &Parser { - self.inner - } -} -impl<'a, Parser: super::Parser<'a>> DerefMut for WithState<'a, '_, Parser> { - fn deref_mut(&mut self) -> &mut Parser { - self.inner - } -} - -impl<'a, Parser: super::Parser<'a>> Drop for WithState<'a, '_, Parser> { - fn drop(&mut self) { - std::mem::swap(self.inner.state_mut(), &mut self.orig_state); - } -} diff --git a/crates/swc_ecma_lexer/src/common/parser/stmt.rs b/crates/swc_ecma_lexer/src/common/parser/stmt.rs deleted file mode 100644 index 307e029e6bdc..000000000000 --- a/crates/swc_ecma_lexer/src/common/parser/stmt.rs +++ /dev/null @@ -1,1414 +0,0 @@ -use swc_common::{BytePos, Span, Spanned}; -use swc_ecma_ast::*; - -use super::{ - buffer::Buffer, - class_and_fn::parse_fn_decl, - expr::parse_assignment_expr, - is_directive::IsDirective, - pat::parse_binding_pat_or_ident, - typescript::{try_parse_ts_type_ann, ts_look_ahead}, - PResult, Parser, -}; -use crate::{ - common::{ - context::Context, - lexer::token::TokenFactory, - parser::{ - class_and_fn::{parse_async_fn_decl, parse_class_decl, parse_decorators}, - eof_error, - expr::{parse_await_expr, parse_bin_op_recursively, parse_for_head_prefix}, - ident::{parse_binding_ident, parse_label_ident}, - pat::reparse_expr_as_pat, - pat_type::PatType, - typescript::{ - parse_ts_enum_decl, parse_ts_expr_stmt, parse_ts_interface_decl, parse_ts_type, - parse_ts_type_alias_decl, - }, - TokenAndSpan, - }, - }, - error::{Error, SyntaxError}, -}; - -#[allow(clippy::enum_variant_names)] -pub enum TempForHead { - For { - init: Option, - test: Option>, - update: Option>, - }, - ForIn { - left: ForHead, - right: Box, - }, - ForOf { - left: ForHead, - right: Box, - }, -} - -fn parse_normal_for_head<'a, P: Parser<'a>>( - p: &mut P, - init: Option, -) -> PResult { - let test = if p.input_mut().eat(&P::Token::SEMI) { - None - } else { - let test = p.allow_in_expr(|p| p.parse_expr()).map(Some)?; - p.input_mut().eat(&P::Token::SEMI); - test - }; - - let update = if p.input().is(&P::Token::RPAREN) { - None - } else { - p.allow_in_expr(|p| p.parse_expr()).map(Some)? - }; - - Ok(TempForHead::For { init, test, update }) -} - -fn parse_for_each_head<'a, P: Parser<'a>>(p: &mut P, left: ForHead) -> PResult { - let is_of = p.input().cur().is_of(); - p.bump(); - if is_of { - let right = p.allow_in_expr(parse_assignment_expr)?; - Ok(TempForHead::ForOf { left, right }) - } else { - if let ForHead::UsingDecl(d) = &left { - p.emit_err(d.span, SyntaxError::UsingDeclNotAllowedForForInLoop) - } - let right = p.allow_in_expr(|p| p.parse_expr())?; - Ok(TempForHead::ForIn { left, right }) - } -} - -pub fn parse_return_stmt<'a, P: Parser<'a>>(p: &mut P) -> PResult { - let start = p.cur_pos(); - - p.assert_and_bump(&P::Token::RETURN); - - let arg = if p.is_general_semi() { - None - } else { - p.allow_in_expr(|p| p.parse_expr()).map(Some)? - }; - p.expect_general_semi()?; - let stmt = Ok(ReturnStmt { - span: p.span(start), - arg, - } - .into()); - - if !p.ctx().contains(Context::InFunction) && !p.input().syntax().allow_return_outside_function() - { - p.emit_err(p.span(start), SyntaxError::ReturnNotAllowed); - } - - stmt -} - -fn parse_var_declarator<'a, P: Parser<'a>>( - p: &mut P, - for_loop: bool, - kind: VarDeclKind, -) -> PResult { - let start = p.cur_pos(); - - let is_let_or_const = matches!(kind, VarDeclKind::Let | VarDeclKind::Const); - - let mut name = parse_binding_pat_or_ident(p, is_let_or_const)?; - - let definite = if p.input().syntax().typescript() { - match name { - Pat::Ident(..) => p.input_mut().eat(&P::Token::BANG), - _ => false, - } - } else { - false - }; - - // Typescript extension - if p.input().syntax().typescript() && p.input().is(&P::Token::COLON) { - let type_annotation = try_parse_ts_type_ann(p)?; - match name { - Pat::Array(ArrayPat { - ref mut type_ann, .. - }) - | Pat::Ident(BindingIdent { - ref mut type_ann, .. - }) - | Pat::Object(ObjectPat { - ref mut type_ann, .. - }) - | Pat::Rest(RestPat { - ref mut type_ann, .. - }) => { - *type_ann = type_annotation; - } - _ => unreachable!("invalid syntax: Pat: {:?}", name), - } - } - - //FIXME: This is wrong. Should check in/of only on first loop. - let cur = p.input().cur(); - let init = if !for_loop || !(cur.is_in() || cur.is_of()) { - if p.input_mut().eat(&P::Token::EQUAL) { - let expr = parse_assignment_expr(p)?; - let expr = p.verify_expr(expr)?; - - Some(expr) - } else { - // Destructuring bindings require initializers, but - // typescript allows `declare` vars not to have initializers. - if p.ctx().contains(Context::InDeclare) { - None - } else if kind == VarDeclKind::Const - && !for_loop - && !p.ctx().contains(Context::InDeclare) - { - p.emit_err( - p.span(start), - SyntaxError::ConstDeclarationsRequireInitialization, - ); - - None - } else { - match name { - Pat::Ident(..) => None, - _ => { - syntax_error!(p, p.span(start), SyntaxError::PatVarWithoutInit) - } - } - } - } - } else { - // e.g. for(let a;;) - None - }; - - Ok(VarDeclarator { - span: p.span(start), - name, - init, - definite, - }) -} - -pub fn parse_var_stmt<'a, P: Parser<'a>>(p: &mut P, for_loop: bool) -> PResult> { - let start = p.cur_pos(); - let t = p.input().cur(); - let kind = if t.is_const() { - VarDeclKind::Const - } else if t.is_let() { - VarDeclKind::Let - } else if t.is_var() { - VarDeclKind::Var - } else { - unreachable!() - }; - p.bump(); - let var_span = p.span(start); - let should_include_in = kind != VarDeclKind::Var || !for_loop; - - if p.syntax().typescript() && for_loop { - let cur = p.input().cur(); - let res: PResult = if cur.is_in() || cur.is_of() { - ts_look_ahead(p, |p| { - // - if !p.input_mut().eat(&P::Token::OF) && !p.input_mut().eat(&P::Token::IN) { - return Ok(false); - } - - parse_assignment_expr(p)?; - expect!(p, &P::Token::RPAREN); - - Ok(true) - }) - } else { - Ok(false) - }; - - match res { - Ok(true) => { - let pos = var_span.hi(); - let span = Span::new_with_checked(pos, pos); - p.emit_err(span, SyntaxError::TS1123); - - return Ok(Box::new(VarDecl { - span: p.span(start), - kind, - declare: false, - decls: Vec::new(), - ..Default::default() - })); - } - Err(..) => {} - _ => {} - } - } - - let mut decls = Vec::with_capacity(4); - loop { - // Handle - // var a,; - // - // NewLine is ok - if p.input().is(&P::Token::SEMI) { - let prev_span = p.input().prev_span(); - let span = if prev_span == var_span { - Span::new_with_checked(prev_span.hi, prev_span.hi) - } else { - prev_span - }; - p.emit_err(span, SyntaxError::TS1009); - break; - } - - let decl = if should_include_in { - p.do_inside_of_context(Context::IncludeInExpr, |p| { - parse_var_declarator(p, for_loop, kind) - }) - } else { - parse_var_declarator(p, for_loop, kind) - }?; - - decls.push(decl); - - if !p.input_mut().eat(&P::Token::COMMA) { - break; - } - } - - if !for_loop && !p.eat_general_semi() { - p.emit_err(p.input().cur_span(), SyntaxError::TS1005); - - let _ = p.parse_expr(); - - while !p.eat_general_semi() { - p.bump(); - - if p.input().cur().is_error() { - break; - } - } - } - - Ok(Box::new(VarDecl { - span: p.span(start), - declare: false, - kind, - decls, - ..Default::default() - })) -} - -pub fn parse_using_decl<'a, P: Parser<'a>>( - p: &mut P, - start: BytePos, - is_await: bool, -) -> PResult>> { - // using - // reader = init() - - // is two statements - if p.input_mut().has_linebreak_between_cur_and_peeked() { - return Ok(None); - } - - if !p.peek_is_ident_ref() { - return Ok(None); - } - - p.assert_and_bump(&P::Token::USING); - - let mut decls = Vec::new(); - loop { - // Handle - // var a,; - // - // NewLine is ok - if p.input().is(&P::Token::SEMI) { - let span = p.input().prev_span(); - p.emit_err(span, SyntaxError::TS1009); - break; - } - - decls.push(parse_var_declarator(p, false, VarDeclKind::Var)?); - if !p.input_mut().eat(&P::Token::COMMA) { - break; - } - } - - if !p.syntax().explicit_resource_management() { - p.emit_err(p.span(start), SyntaxError::UsingDeclNotEnabled); - } - - if !p.ctx().contains(Context::AllowUsingDecl) { - p.emit_err(p.span(start), SyntaxError::UsingDeclNotAllowed); - } - - for decl in &decls { - match decl.name { - Pat::Ident(..) => {} - _ => { - p.emit_err(p.span(start), SyntaxError::InvalidNameInUsingDecl); - } - } - - if decl.init.is_none() { - p.emit_err(p.span(start), SyntaxError::InitRequiredForUsingDecl); - } - } - - p.expect_general_semi()?; - - Ok(Some(Box::new(UsingDecl { - span: p.span(start), - is_await, - decls, - }))) -} - -pub fn parse_for_head<'a, P: Parser<'a>>(p: &mut P) -> PResult { - // let strict = p.ctx().contains(Context::Strict); - - let cur = p.input().cur(); - if cur.is_const() - || cur.is_var() - || (p.input().is(&P::Token::LET) && peek!(p).map_or(false, |v| v.follows_keyword_let())) - { - let decl = parse_var_stmt(p, true)?; - - let cur = p.input().cur(); - if cur.is_of() || cur.is_in() { - if decl.decls.len() != 1 { - for d in decl.decls.iter().skip(1) { - p.emit_err(d.name.span(), SyntaxError::TooManyVarInForInHead); - } - } else { - if (p.ctx().contains(Context::Strict) || p.input().is(&P::Token::OF)) - && decl.decls[0].init.is_some() - { - p.emit_err( - decl.decls[0].name.span(), - SyntaxError::VarInitializerInForInHead, - ); - } - - if p.syntax().typescript() { - let type_ann = match decl.decls[0].name { - Pat::Ident(ref v) => Some(&v.type_ann), - Pat::Array(ref v) => Some(&v.type_ann), - Pat::Rest(ref v) => Some(&v.type_ann), - Pat::Object(ref v) => Some(&v.type_ann), - _ => None, - }; - - if let Some(type_ann) = type_ann { - if type_ann.is_some() { - p.emit_err(decl.decls[0].name.span(), SyntaxError::TS2483); - } - } - } - } - - return parse_for_each_head(p, ForHead::VarDecl(decl)); - } - - expect!(p, &P::Token::SEMI); - return parse_normal_for_head(p, Some(VarDeclOrExpr::VarDecl(decl))); - } - - if p.input_mut().eat(&P::Token::SEMI) { - return parse_normal_for_head(p, None); - } - - let start = p.cur_pos(); - let init = p.disallow_in_expr(parse_for_head_prefix)?; - - let mut is_using_decl = false; - let mut is_await_using_decl = false; - - if p.input().syntax().explicit_resource_management() { - // using foo - let mut maybe_using_decl = init.is_ident_ref_to("using"); - let mut maybe_await_using_decl = false; - - // await using foo - if !maybe_using_decl - && init - .as_await_expr() - .filter(|e| e.arg.is_ident_ref_to("using")) - .is_some() - { - maybe_using_decl = true; - maybe_await_using_decl = true; - } - - if maybe_using_decl - && !p.input().is(&P::Token::OF) - && (peek!(p).is_some_and(|peek| peek.is_of() || peek.is_in())) - { - is_using_decl = maybe_using_decl; - is_await_using_decl = maybe_await_using_decl; - } - } - - if is_using_decl { - let name = parse_binding_ident(p, false)?; - let decl = VarDeclarator { - name: name.into(), - span: p.span(start), - init: None, - definite: false, - }; - - let pat = Box::new(UsingDecl { - span: p.span(start), - is_await: is_await_using_decl, - decls: vec![decl], - }); - - let cur = p.input().cur(); - if cur.is_error() { - let err = p.input_mut().expect_error_token_and_bump(); - return Err(err); - } else if cur.is_eof() { - return Err(eof_error(p)); - } - - return parse_for_each_head(p, ForHead::UsingDecl(pat)); - } - - // for (a of b) - let cur = p.input().cur(); - if cur.is_of() || cur.is_in() { - let is_in = p.input().is(&P::Token::IN); - - let pat = reparse_expr_as_pat(p, PatType::AssignPat, init)?; - - // for ({} in foo) is invalid - if p.input().syntax().typescript() && is_in { - match pat { - Pat::Ident(..) => {} - Pat::Expr(..) => {} - ref v => p.emit_err(v.span(), SyntaxError::TS2491), - } - } - - return parse_for_each_head(p, ForHead::Pat(Box::new(pat))); - } - - expect!(p, &P::Token::SEMI); - - let init = p.verify_expr(init)?; - parse_normal_for_head(p, Some(VarDeclOrExpr::Expr(init))) -} - -fn parse_for_stmt<'a, P: Parser<'a>>(p: &mut P) -> PResult { - let start = p.cur_pos(); - - p.assert_and_bump(&P::Token::FOR); - let await_start = p.cur_pos(); - let await_token = if p.input_mut().eat(&P::Token::AWAIT) { - Some(p.span(await_start)) - } else { - None - }; - expect!(p, &P::Token::LPAREN); - - let head = p.do_inside_of_context(Context::ForLoopInit, |p| { - if await_token.is_some() { - p.do_inside_of_context(Context::ForAwaitLoopInit, parse_for_head) - } else { - p.do_outside_of_context(Context::ForAwaitLoopInit, parse_for_head) - } - })?; - - expect!(p, &P::Token::RPAREN); - - let body = p - .do_inside_of_context( - Context::IsBreakAllowed.union(Context::IsContinueAllowed), - |p| p.do_outside_of_context(Context::TopLevel, parse_stmt), - ) - .map(Box::new)?; - - let span = p.span(start); - Ok(match head { - TempForHead::For { init, test, update } => { - if let Some(await_token) = await_token { - syntax_error!(p, await_token, SyntaxError::AwaitForStmt); - } - - ForStmt { - span, - init, - test, - update, - body, - } - .into() - } - TempForHead::ForIn { left, right } => { - if let Some(await_token) = await_token { - syntax_error!(p, await_token, SyntaxError::AwaitForStmt); - } - - ForInStmt { - span, - left, - right, - body, - } - .into() - } - TempForHead::ForOf { left, right } => ForOfStmt { - span, - is_await: await_token.is_some(), - left, - right, - body, - } - .into(), - }) -} - -pub fn parse_stmt<'a>(p: &mut impl Parser<'a>) -> PResult { - trace_cur!(p, parse_stmt); - parse_stmt_like(p, false, handle_import_export) -} - -/// Utility function used to parse large if else statements iteratively. -/// -/// THis function is recursive, but it is very cheap so stack overflow will -/// not occur. -fn adjust_if_else_clause<'a, P: Parser<'a>>(p: &mut P, cur: &mut IfStmt, alt: Box) { - cur.span = p.span(cur.span.lo); - - if let Some(Stmt::If(prev_alt)) = cur.alt.as_deref_mut() { - adjust_if_else_clause(p, prev_alt, alt) - } else { - debug_assert_eq!(cur.alt, None); - cur.alt = Some(alt); - } -} - -fn parse_if_stmt<'a, P: Parser<'a>>(p: &mut P) -> PResult { - let start = p.cur_pos(); - - p.assert_and_bump(&P::Token::IF); - let if_token = p.input().prev_span(); - - expect!(p, &P::Token::LPAREN); - - let test = p - .do_outside_of_context(Context::IgnoreElseClause, |p| { - p.allow_in_expr(|p| p.parse_expr()) - }) - .map_err(|err| { - Error::new( - err.span(), - SyntaxError::WithLabel { - inner: Box::new(err), - span: if_token, - note: "Tried to parse the condition for an if statement", - }, - ) - })?; - - expect!(p, &P::Token::RPAREN); - - let cons = { - // Prevent stack overflow - crate::maybe_grow(256 * 1024, 1024 * 1024, || { - // // Annex B - // if !p.ctx().contains(Context::Strict) && p.input().is(&P::Token::FUNCTION) { - // // TODO: report error? - // } - p.do_outside_of_context( - Context::IgnoreElseClause.union(Context::TopLevel), - parse_stmt, - ) - .map(Box::new) - })? - }; - - // We parse `else` branch iteratively, to avoid stack overflow - // See https://github.com/swc-project/swc/pull/3961 - - let alt = if p.ctx().contains(Context::IgnoreElseClause) { - None - } else { - let mut cur = None; - - let last = loop { - if !p.input_mut().eat(&P::Token::ELSE) { - break None; - } - - if !p.input().is(&P::Token::IF) { - // As we eat `else` above, we need to parse statement once. - let last = p.do_outside_of_context( - Context::IgnoreElseClause.union(Context::TopLevel), - parse_stmt, - )?; - break Some(last); - } - - // We encountered `else if` - - let alt = p.do_inside_of_context(Context::IgnoreElseClause, parse_if_stmt)?; - - match &mut cur { - Some(cur) => { - adjust_if_else_clause(p, cur, Box::new(alt.into())); - } - _ => { - cur = Some(alt); - } - } - }; - - match cur { - Some(mut cur) => { - if let Some(last) = last { - adjust_if_else_clause(p, &mut cur, Box::new(last)); - } - Some(cur.into()) - } - _ => last, - } - } - .map(Box::new); - - let span = p.span(start); - Ok(IfStmt { - span, - test, - cons, - alt, - }) -} - -fn parse_throw_stmt<'a, P: Parser<'a>>(p: &mut P) -> PResult { - let start = p.cur_pos(); - - p.assert_and_bump(&P::Token::THROW); - - if p.input().had_line_break_before_cur() { - // TODO: Suggest throw arg; - syntax_error!(p, SyntaxError::LineBreakInThrow); - } - - let arg = p.allow_in_expr(|p| p.parse_expr())?; - p.expect_general_semi()?; - - let span = p.span(start); - Ok(ThrowStmt { span, arg }.into()) -} - -fn parse_with_stmt<'a, P: Parser<'a>>(p: &mut P) -> PResult { - if p.syntax().typescript() { - let span = p.input().cur_span(); - p.emit_err(span, SyntaxError::TS2410); - } - - { - let span = p.input().cur_span(); - p.emit_strict_mode_err(span, SyntaxError::WithInStrict); - } - - let start = p.cur_pos(); - - p.assert_and_bump(&P::Token::WITH); - - expect!(p, &P::Token::LPAREN); - let obj = p.allow_in_expr(|p| p.parse_expr())?; - expect!(p, &P::Token::RPAREN); - - let body = p - .do_inside_of_context(Context::InFunction, |p| { - p.do_outside_of_context(Context::TopLevel, parse_stmt) - }) - .map(Box::new)?; - - let span = p.span(start); - Ok(WithStmt { span, obj, body }.into()) -} - -fn parse_while_stmt<'a, P: Parser<'a>>(p: &mut P) -> PResult { - let start = p.cur_pos(); - - p.assert_and_bump(&P::Token::WHILE); - - expect!(p, &P::Token::LPAREN); - let test = p.allow_in_expr(|p| p.parse_expr())?; - expect!(p, &P::Token::RPAREN); - - let body = p - .do_inside_of_context( - Context::IsBreakAllowed.union(Context::IsContinueAllowed), - |p| p.do_outside_of_context(Context::TopLevel, parse_stmt), - ) - .map(Box::new)?; - - let span = p.span(start); - Ok(WhileStmt { span, test, body }.into()) -} - -/// It's optional since es2019 -fn parse_catch_param<'a, P: Parser<'a>>(p: &mut P) -> PResult> { - if p.input_mut().eat(&P::Token::LPAREN) { - let mut pat = parse_binding_pat_or_ident(p, false)?; - - let type_ann_start = p.cur_pos(); - - if p.syntax().typescript() && p.input_mut().eat(&P::Token::COLON) { - let ty = p.do_inside_of_context(Context::InType, parse_ts_type)?; - // p.emit_err(ty.span(), SyntaxError::TS1196); - - match &mut pat { - Pat::Ident(BindingIdent { type_ann, .. }) - | Pat::Array(ArrayPat { type_ann, .. }) - | Pat::Rest(RestPat { type_ann, .. }) - | Pat::Object(ObjectPat { type_ann, .. }) => { - *type_ann = Some(Box::new(TsTypeAnn { - span: p.span(type_ann_start), - type_ann: ty, - })); - } - Pat::Assign(..) => {} - Pat::Invalid(_) => {} - Pat::Expr(_) => {} - #[cfg(swc_ast_unknown)] - _ => {} - } - } - expect!(p, &P::Token::RPAREN); - Ok(Some(pat)) - } else { - Ok(None) - } -} - -fn parse_do_stmt<'a, P: Parser<'a>>(p: &mut P) -> PResult { - let start = p.cur_pos(); - - p.assert_and_bump(&P::Token::DO); - - let body = p - .do_inside_of_context( - Context::IsBreakAllowed.union(Context::IsContinueAllowed), - |p| p.do_outside_of_context(Context::TopLevel, parse_stmt), - ) - .map(Box::new)?; - - expect!(p, &P::Token::WHILE); - expect!(p, &P::Token::LPAREN); - - let test = p.allow_in_expr(|p| p.parse_expr())?; - - expect!(p, &P::Token::RPAREN); - - // We *may* eat semicolon. - let _ = p.eat_general_semi(); - - let span = p.span(start); - - Ok(DoWhileStmt { span, test, body }.into()) -} - -fn parse_labelled_stmt<'a, P: Parser<'a>>(p: &mut P, l: Ident) -> PResult { - p.do_inside_of_context(Context::IsBreakAllowed, |p| { - p.do_outside_of_context(Context::AllowUsingDecl, |p| { - let start = l.span.lo(); - - let mut errors = Vec::new(); - for lb in &p.state().labels { - if l.sym == *lb { - errors.push(Error::new( - l.span, - SyntaxError::DuplicateLabel(l.sym.clone()), - )); - } - } - p.state_mut().labels.push(l.sym.clone()); - - let body = Box::new(if p.input().is(&P::Token::FUNCTION) { - let f = parse_fn_decl(p, Vec::new())?; - if let Decl::Fn(FnDecl { function, .. }) = &f { - if p.ctx().contains(Context::Strict) { - p.emit_err(function.span, SyntaxError::LabelledFunctionInStrict) - } - if function.is_generator || function.is_async { - p.emit_err(function.span, SyntaxError::LabelledGeneratorOrAsync) - } - } - - f.into() - } else { - p.do_outside_of_context(Context::TopLevel, parse_stmt)? - }); - - for err in errors { - p.emit_error(err); - } - - { - let pos = p.state().labels.iter().position(|v| v == &l.sym); - if let Some(pos) = pos { - p.state_mut().labels.remove(pos); - } - } - - Ok(LabeledStmt { - span: p.span(start), - label: l, - body, - } - .into()) - }) - }) -} - -pub fn parse_block<'a, P: Parser<'a>>(p: &mut P, allow_directives: bool) -> PResult { - let start = p.cur_pos(); - - expect!(p, &P::Token::LBRACE); - - let stmts = p.do_outside_of_context(Context::TopLevel, |p| { - parse_stmt_block_body(p, allow_directives, Some(&P::Token::RBRACE)) - })?; - - let span = p.span(start); - Ok(BlockStmt { - span, - stmts, - ctxt: Default::default(), - }) -} - -fn parse_finally_block<'a, P: Parser<'a>>(p: &mut P) -> PResult> { - Ok(if p.input_mut().eat(&P::Token::FINALLY) { - parse_block(p, false).map(Some)? - } else { - None - }) -} - -fn parse_catch_clause<'a, P: Parser<'a>>(p: &mut P) -> PResult> { - let start = p.cur_pos(); - Ok(if p.input_mut().eat(&P::Token::CATCH) { - let param = parse_catch_param(p)?; - parse_block(p, false) - .map(|body| CatchClause { - span: p.span(start), - param, - body, - }) - .map(Some)? - } else { - None - }) -} - -fn parse_try_stmt<'a, P: Parser<'a>>(p: &mut P) -> PResult { - let start = p.cur_pos(); - p.assert_and_bump(&P::Token::TRY); - - let block = parse_block(p, false)?; - - let catch_start = p.cur_pos(); - let handler = parse_catch_clause(p)?; - let finalizer = parse_finally_block(p)?; - - if handler.is_none() && finalizer.is_none() { - p.emit_err( - Span::new_with_checked(catch_start, catch_start), - SyntaxError::TS1005, - ); - } - - let span = p.span(start); - Ok(TryStmt { - span, - block, - handler, - finalizer, - } - .into()) -} - -fn parse_switch_stmt<'a, P: Parser<'a>>(p: &mut P) -> PResult { - let switch_start = p.cur_pos(); - - p.assert_and_bump(&P::Token::SWITCH); - - expect!(p, &P::Token::LPAREN); - let discriminant = p.allow_in_expr(|p| p.parse_expr())?; - expect!(p, &P::Token::RPAREN); - - let mut cases = Vec::new(); - let mut span_of_previous_default = None; - - expect!(p, &P::Token::LBRACE); - - p.do_inside_of_context(Context::IsBreakAllowed, |p| { - while { - let cur = p.input().cur(); - cur.is_case() || cur.is_default() - } { - let mut cons = Vec::new(); - let is_case = p.input().is(&P::Token::CASE); - let case_start = p.cur_pos(); - p.bump(); - let test = if is_case { - p.allow_in_expr(|p| p.parse_expr()).map(Some)? - } else { - if let Some(previous) = span_of_previous_default { - syntax_error!(p, SyntaxError::MultipleDefault { previous }); - } - span_of_previous_default = Some(p.span(case_start)); - - None - }; - expect!(p, &P::Token::COLON); - - while { - let cur = p.input().cur(); - !(cur.is_case() || cur.is_default() || cur.is_rbrace()) - } { - cons.push(p.do_outside_of_context(Context::TopLevel, parse_stmt_list_item)?); - } - - cases.push(SwitchCase { - span: Span::new_with_checked(case_start, p.input().prev_span().hi), - test, - cons, - }); - } - - Ok(()) - })?; - - // eof or rbrace - expect!(p, &P::Token::RBRACE); - - Ok(SwitchStmt { - span: p.span(switch_start), - discriminant, - cases, - } - .into()) -} - -/// Parse a statement and maybe a declaration. -pub fn parse_stmt_list_item<'a>(p: &mut impl Parser<'a>) -> PResult { - trace_cur!(p, parse_stmt_list_item); - parse_stmt_like(p, true, handle_import_export) -} - -/// Parse a statement, declaration or module item. -pub fn parse_stmt_like<'a, P: Parser<'a>, Type: IsDirective + From>( - p: &mut P, - include_decl: bool, - handle_import_export: impl Fn(&mut P, Vec) -> PResult, -) -> PResult { - trace_cur!(p, parse_stmt_like); - - debug_tracing!(p, "parse_stmt_like"); - - let start = p.cur_pos(); - let decorators = if p.input().get_cur().token() == &P::Token::AT { - parse_decorators(p, true)? - } else { - vec![] - }; - - let cur = p.input().cur(); - if cur.is_import() || cur.is_export() { - return handle_import_export(p, decorators); - } - - p.do_outside_of_context(Context::WillExpectColonForCond, |p| { - p.do_inside_of_context(Context::AllowUsingDecl, |p| { - parse_stmt_internal(p, start, include_decl, decorators) - }) - }) - .map(From::from) -} - -fn handle_import_export<'a, P: Parser<'a>>(p: &mut P, _: Vec) -> PResult { - let start = p.cur_pos(); - if p.input().is(&P::Token::IMPORT) && peek!(p).is_some_and(|peek| peek.is_lparen()) { - let expr = p.parse_expr()?; - - p.eat_general_semi(); - - return Ok(ExprStmt { - span: p.span(start), - expr, - } - .into()); - } - - if p.input().is(&P::Token::IMPORT) && peek!(p).is_some_and(|peek| peek.is_dot()) { - let expr = p.parse_expr()?; - - p.eat_general_semi(); - - return Ok(ExprStmt { - span: p.span(start), - expr, - } - .into()); - } - - syntax_error!(p, SyntaxError::ImportExportInScript); -} - -/// `parseStatementContent` -fn parse_stmt_internal<'a, P: Parser<'a>>( - p: &mut P, - start: BytePos, - include_decl: bool, - decorators: Vec, -) -> PResult { - trace_cur!(p, parse_stmt_internal); - - let is_typescript = p.input().syntax().typescript(); - - if is_typescript - && p.input().is(&P::Token::CONST) - && peek!(p).is_some_and(|peek| peek.is_enum()) - { - p.assert_and_bump(&P::Token::CONST); - p.assert_and_bump(&P::Token::ENUM); - return parse_ts_enum_decl(p, start, true) - .map(Decl::from) - .map(Stmt::from); - } - - let top_level = p.ctx().contains(Context::TopLevel); - - let cur = p.input().cur().clone(); - - if cur.is_await() && (include_decl || top_level) { - if top_level { - p.mark_found_module_item(); - if !p.ctx().contains(Context::CanBeModule) { - p.emit_err(p.input().cur_span(), SyntaxError::TopLevelAwaitInScript); - } - } - - if peek!(p).is_some_and(|peek| peek.is_using()) { - let eaten_await = Some(p.input().cur_pos()); - p.assert_and_bump(&P::Token::AWAIT); - let v = parse_using_decl(p, start, true)?; - if let Some(v) = v { - return Ok(v.into()); - } - - let expr = parse_await_expr(p, eaten_await)?; - let expr = p.allow_in_expr(|p| parse_bin_op_recursively(p, expr, 0))?; - p.eat_general_semi(); - - let span = p.span(start); - return Ok(ExprStmt { span, expr }.into()); - } - } else if cur.is_break() || cur.is_continue() { - let is_break = p.input().is(&P::Token::BREAK); - p.bump(); - let label = if p.eat_general_semi() { - None - } else { - let i = parse_label_ident(p).map(Some)?; - p.expect_general_semi()?; - i - }; - let span = p.span(start); - if is_break { - if label.is_some() && !p.state().labels.contains(&label.as_ref().unwrap().sym) { - p.emit_err(span, SyntaxError::TS1116); - } else if !p.ctx().contains(Context::IsBreakAllowed) { - p.emit_err(span, SyntaxError::TS1105); - } - } else if !p.ctx().contains(Context::IsContinueAllowed) { - p.emit_err(span, SyntaxError::TS1115); - } else if label.is_some() && !p.state().labels.contains(&label.as_ref().unwrap().sym) { - p.emit_err(span, SyntaxError::TS1107); - } - return Ok(if is_break { - BreakStmt { span, label }.into() - } else { - ContinueStmt { span, label }.into() - }); - } else if cur.is_debugger() { - p.bump(); - p.expect_general_semi()?; - return Ok(DebuggerStmt { - span: p.span(start), - } - .into()); - } else if cur.is_do() { - return parse_do_stmt(p); - } else if cur.is_for() { - return parse_for_stmt(p); - } else if cur.is_function() { - if !include_decl { - p.emit_err(p.input().cur_span(), SyntaxError::DeclNotAllowed); - } - return parse_fn_decl(p, decorators).map(Stmt::from); - } else if cur.is_class() { - if !include_decl { - p.emit_err(p.input().cur_span(), SyntaxError::DeclNotAllowed); - } - return parse_class_decl(p, start, start, decorators, false).map(Stmt::from); - } else if cur.is_if() { - return parse_if_stmt(p).map(Stmt::If); - } else if cur.is_return() { - return parse_return_stmt(p); - } else if cur.is_switch() { - return parse_switch_stmt(p); - } else if cur.is_throw() { - return parse_throw_stmt(p); - } else if cur.is_catch() { - // Error recovery - let span = p.input().cur_span(); - p.emit_err(span, SyntaxError::TS1005); - - let _ = parse_catch_clause(p); - let _ = parse_finally_block(p); - - return Ok(ExprStmt { - span, - expr: Invalid { span }.into(), - } - .into()); - } else if cur.is_finally() { - // Error recovery - let span = p.input().cur_span(); - p.emit_err(span, SyntaxError::TS1005); - - let _ = parse_finally_block(p); - - return Ok(ExprStmt { - span, - expr: Invalid { span }.into(), - } - .into()); - } else if cur.is_try() { - return parse_try_stmt(p); - } else if cur.is_with() { - return parse_with_stmt(p); - } else if cur.is_while() { - return parse_while_stmt(p); - } else if cur.is_var() || (cur.is_const() && include_decl) { - let v = parse_var_stmt(p, false)?; - return Ok(v.into()); - } else if cur.is_let() && include_decl { - // 'let' can start an identifier reference. - let is_keyword = match peek!(p) { - Some(t) => t.follows_keyword_let(), - _ => false, - }; - - if is_keyword { - let v = parse_var_stmt(p, false)?; - return Ok(v.into()); - } - } else if cur.is_using() && include_decl { - let v = parse_using_decl(p, start, false)?; - if let Some(v) = v { - return Ok(v.into()); - } - } else if cur.is_interface() - && is_typescript - && peek!(p).is_some_and(|peek| peek.is_word()) - && !p.input_mut().has_linebreak_between_cur_and_peeked() - { - let start = p.input().cur_pos(); - p.bump(); - return Ok(parse_ts_interface_decl(p, start)?.into()); - } else if cur.is_type() - && is_typescript - && peek!(p).is_some_and(|peek| peek.is_word()) - && !p.input_mut().has_linebreak_between_cur_and_peeked() - { - let start = p.input().cur_pos(); - p.bump(); - return Ok(parse_ts_type_alias_decl(p, start)?.into()); - } else if cur.is_enum() - && is_typescript - && peek!(p).is_some_and(|peek| peek.is_word()) - && !p.input_mut().has_linebreak_between_cur_and_peeked() - { - let start = p.input().cur_pos(); - p.bump(); - return Ok(parse_ts_enum_decl(p, start, false)?.into()); - } else if cur.is_lbrace() { - return p - .do_inside_of_context(Context::AllowUsingDecl, |p| parse_block(p, false)) - .map(Stmt::Block); - } else if cur.is_semi() { - p.bump(); - return Ok(EmptyStmt { - span: p.span(start), - } - .into()); - } - - // Handle async function foo() {} - if p.input().is(&P::Token::ASYNC) - && peek!(p).is_some_and(|peek| peek.is_function()) - && !p.input_mut().has_linebreak_between_cur_and_peeked() - { - return parse_async_fn_decl(p, decorators).map(From::from); - } - - // If the statement does not start with a statement keyword or a - // brace, it's an ExpressionStatement or LabeledStatement. We - // simply start parsing an expression, and afterwards, if the - // next token is a colon and the expression was a simple - // Identifier node, we switch to interpreting it as a label. - let expr = p.allow_in_expr(|p| p.parse_expr())?; - - let expr = match *expr { - Expr::Ident(ident) => { - if p.input_mut().eat(&P::Token::COLON) { - return parse_labelled_stmt(p, ident); - } - ident.into() - } - _ => p.verify_expr(expr)?, - }; - if let Expr::Ident(ref ident) = *expr { - if &*ident.sym == "interface" && p.input().had_line_break_before_cur() { - p.emit_strict_mode_err( - ident.span, - SyntaxError::InvalidIdentInStrict(ident.sym.clone()), - ); - - p.eat_general_semi(); - - return Ok(ExprStmt { - span: p.span(start), - expr, - } - .into()); - } - - if p.input().syntax().typescript() { - if let Some(decl) = parse_ts_expr_stmt(p, decorators, ident.clone())? { - return Ok(decl.into()); - } - } - } - - if p.syntax().typescript() { - if let Expr::Ident(ref i) = *expr { - match &*i.sym { - "public" | "static" | "abstract" if p.input_mut().eat(&P::Token::INTERFACE) => { - p.emit_err(i.span, SyntaxError::TS2427); - return parse_ts_interface_decl(p, start) - .map(Decl::from) - .map(Stmt::from); - } - _ => {} - } - } - } - - if p.eat_general_semi() { - Ok(ExprStmt { - span: p.span(start), - expr, - } - .into()) - } else { - let cur = p.input().cur(); - if cur.is_bin_op() { - p.emit_err(p.input().cur_span(), SyntaxError::TS1005); - let expr = parse_bin_op_recursively(p, expr, 0)?; - return Ok(ExprStmt { - span: p.span(start), - expr, - } - .into()); - } - - syntax_error!( - p, - SyntaxError::ExpectedSemiForExprStmt { expr: expr.span() } - ); - } -} - -pub fn parse_stmt_block_body<'a, P: Parser<'a>>( - p: &mut P, - allow_directives: bool, - end: Option<&P::Token>, -) -> PResult> { - parse_block_body(p, allow_directives, end, handle_import_export) -} - -pub(super) fn parse_block_body<'a, P: Parser<'a>, Type: IsDirective + From>( - p: &mut P, - allow_directives: bool, - end: Option<&P::Token>, - handle_import_export: impl Fn(&mut P, Vec) -> PResult, -) -> PResult> { - trace_cur!(p, parse_block_body); - - let mut stmts = Vec::with_capacity(8); - - let has_strict_directive = allow_directives - && (p - .input() - .cur() - .is_str_raw_content("\"use strict\"", p.input()) - || p.input() - .cur() - .is_str_raw_content("'use strict'", p.input())); - - let parse_stmts = |p: &mut P, stmts: &mut Vec| -> PResult<()> { - let is_stmt_start = |p: &mut P| { - let cur = p.input().cur(); - match end { - Some(end) => { - if cur.is_eof() { - let eof_text = p.input_mut().dump_cur(); - p.emit_err( - p.input().cur_span(), - SyntaxError::Expected(format!("{end:?}"), eof_text), - ); - false - } else { - cur != end - } - } - None => !cur.is_eof(), - } - }; - while is_stmt_start(p) { - let stmt = parse_stmt_like(p, true, &handle_import_export)?; - stmts.push(stmt); - } - Ok(()) - }; - - if has_strict_directive { - p.do_inside_of_context(Context::Strict, |p| parse_stmts(p, &mut stmts))?; - } else { - parse_stmts(p, &mut stmts)?; - }; - - if !p.input().cur().is_eof() && end.is_some() { - p.bump(); - } - - Ok(stmts) -} diff --git a/crates/swc_ecma_lexer/src/common/parser/token_and_span.rs b/crates/swc_ecma_lexer/src/common/parser/token_and_span.rs deleted file mode 100644 index 4a0e952bef9f..000000000000 --- a/crates/swc_ecma_lexer/src/common/parser/token_and_span.rs +++ /dev/null @@ -1,10 +0,0 @@ -use swc_common::Span; - -pub trait TokenAndSpan { - type Token; - fn new(token: Self::Token, span: Span, had_line_break: bool) -> Self; - fn token(&self) -> &Self::Token; - fn take_token(self) -> Self::Token; - fn span(&self) -> Span; - fn had_line_break(&self) -> bool; -} diff --git a/crates/swc_ecma_lexer/src/common/parser/typescript.rs b/crates/swc_ecma_lexer/src/common/parser/typescript.rs deleted file mode 100644 index c5e92b7cb15b..000000000000 --- a/crates/swc_ecma_lexer/src/common/parser/typescript.rs +++ /dev/null @@ -1,2936 +0,0 @@ -use std::{fmt::Write, mem}; - -use either::Either; -use swc_atoms::{atom, Atom, Wtf8Atom}; -use swc_common::{BytePos, Span, Spanned}; -use swc_ecma_ast::*; - -use super::{ - class_and_fn::{parse_class_decl, parse_fn_block_or_expr_body, parse_fn_decl}, - eof_error, - expr::{is_start_of_left_hand_side_expr, parse_new_expr}, - ident::parse_maybe_private_name, - is_simple_param_list::IsSimpleParameterList, - make_decl_declare, - stmt::parse_var_stmt, - PResult, Parser, -}; -use crate::{ - common::{ - context::Context, - lexer::token::TokenFactory, - parser::{ - buffer::Buffer, - expr::{parse_assignment_expr, parse_lit, parse_str_lit, parse_subscripts}, - ident::{parse_ident, parse_ident_name}, - module_item::parse_module_item_block_body, - object::parse_object_expr, - pat::{parse_binding_pat_or_ident, parse_formal_params}, - }, - }, - error::SyntaxError, -}; - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum ParsingContext { - EnumMembers, - HeritageClauseElement, - TupleElementTypes, - TypeMembers, - TypeParametersOrArguments, -} - -#[derive(Clone, Copy, PartialEq, Eq)] -enum UnionOrIntersection { - Union, - Intersection, -} - -#[derive(Clone, Copy, PartialEq, Eq)] -enum SignatureParsingMode { - TSCallSignatureDeclaration, - TSConstructSignatureDeclaration, -} - -/// `tsParseList` -fn parse_ts_list<'a, P: Parser<'a>, T, F>( - p: &mut P, - kind: ParsingContext, - mut parse_element: F, -) -> PResult> -where - F: FnMut(&mut P) -> PResult, -{ - debug_assert!(p.input().syntax().typescript()); - let mut buf = Vec::with_capacity(8); - while !is_ts_list_terminator(p, kind) { - // Skipping "parseListElement" from the TS source since that's just for error - // handling. - buf.push(parse_element(p)?); - } - Ok(buf) -} - -/// `tsTryParse` -pub(super) fn try_parse_ts_bool<'a, P: Parser<'a>, F>(p: &mut P, op: F) -> PResult -where - F: FnOnce(&mut P) -> PResult>, -{ - if !p.input().syntax().typescript() { - return Ok(false); - } - - let prev_ignore_error = p.input().get_ctx().contains(Context::IgnoreError); - let checkpoint = p.checkpoint_save(); - p.set_ctx(p.ctx() | Context::IgnoreError); - let res = op(p); - match res { - Ok(Some(res)) if res => { - let mut ctx = p.ctx(); - ctx.set(Context::IgnoreError, prev_ignore_error); - p.input_mut().set_ctx(ctx); - Ok(res) - } - _ => { - p.checkpoint_load(checkpoint); - Ok(false) - } - } -} - -/// `tsParseDelimitedList` -fn parse_ts_delimited_list_inner<'a, P: Parser<'a>, T, F>( - p: &mut P, - kind: ParsingContext, - mut parse_element: F, -) -> PResult> -where - F: FnMut(&mut P) -> PResult<(BytePos, T)>, -{ - debug_assert!(p.input().syntax().typescript()); - let mut buf = Vec::new(); - loop { - trace_cur!(p, parse_ts_delimited_list_inner__element); - - if is_ts_list_terminator(p, kind) { - break; - } - - let (_, element) = parse_element(p)?; - buf.push(element); - - if p.input_mut().eat(&P::Token::COMMA) { - continue; - } - - if is_ts_list_terminator(p, kind) { - break; - } - - if kind == ParsingContext::EnumMembers { - let expect = P::Token::COMMA; - let cur = p.input().cur(); - let cur = cur.clone().to_string(p.input()); - p.emit_err( - p.input().cur_span(), - SyntaxError::Expected(format!("{expect:?}"), cur), - ); - continue; - } - // This will fail with an error about a missing comma - expect!(p, &P::Token::COMMA); - } - - Ok(buf) -} - -/// In no lexer context -pub(crate) fn ts_in_no_context<'a, P: Parser<'a>, T, F>(p: &mut P, op: F) -> PResult -where - F: FnOnce(&mut P) -> PResult, -{ - debug_assert!(p.input().syntax().typescript()); - trace_cur!(p, ts_in_no_context__before); - let saved = std::mem::take(p.input_mut().token_context_mut()); - p.input_mut().token_context_mut().push(saved.0[0]); - debug_assert_eq!(p.input().token_context().len(), 1); - let res = op(p); - p.input_mut().set_token_context(saved); - trace_cur!(p, ts_in_no_context__after); - res -} - -/// `tsIsListTerminator` -fn is_ts_list_terminator<'a>(p: &mut impl Parser<'a>, kind: ParsingContext) -> bool { - debug_assert!(p.input().syntax().typescript()); - let cur = p.input().cur(); - match kind { - ParsingContext::EnumMembers | ParsingContext::TypeMembers => cur.is_rbrace(), - ParsingContext::HeritageClauseElement => { - cur.is_lbrace() || cur.is_implements() || cur.is_extends() - } - ParsingContext::TupleElementTypes => cur.is_rbracket(), - ParsingContext::TypeParametersOrArguments => cur.is_greater(), - } -} - -/// `tsNextTokenCanFollowModifier` -pub(super) fn ts_next_token_can_follow_modifier<'a>(p: &mut impl Parser<'a>) -> bool { - debug_assert!(p.input().syntax().typescript()); - // Note: TypeScript's implementation is much more complicated because - // more things are considered modifiers there. - // This implementation only handles modifiers not handled by @babel/parser - // itself. And "static". TODO: Would be nice to avoid lookahead. Want a - // hasLineBreakUpNext() method... - p.bump(); - - let cur = p.input().cur(); - !p.input().had_line_break_before_cur() && cur.is_lbracket() - || cur.is_lbrace() - || cur.is_star() - || cur.is_dotdotdot() - || cur.is_hash() - || cur.is_word() - || cur.is_str() - || cur.is_num() - || cur.is_bigint() -} - -/// `tsTryParse` -pub fn try_parse_ts<'a, P: Parser<'a>, T, F>(p: &mut P, op: F) -> Option -where - F: FnOnce(&mut P) -> PResult>, -{ - if !p.input().syntax().typescript() { - return None; - } - debug_tracing!(p, "try_parse_ts"); - - trace_cur!(p, try_parse_ts); - - let prev_ignore_error = p.input().get_ctx().contains(Context::IgnoreError); - let checkpoint = p.checkpoint_save(); - p.set_ctx(p.ctx() | Context::IgnoreError); - let res = op(p); - match res { - Ok(Some(res)) => { - trace_cur!(p, try_parse_ts__success_value); - let mut ctx = p.ctx(); - ctx.set(Context::IgnoreError, prev_ignore_error); - p.input_mut().set_ctx(ctx); - Some(res) - } - Ok(None) => { - trace_cur!(p, try_parse_ts__success_no_value); - p.checkpoint_load(checkpoint); - None - } - Err(..) => { - trace_cur!(p, try_parse_ts__fail); - p.checkpoint_load(checkpoint); - None - } - } -} - -/// `tsParseTypeMemberSemicolon` -fn parse_ts_type_member_semicolon<'a, P: Parser<'a>>(p: &mut P) -> PResult<()> { - debug_assert!(p.input().syntax().typescript()); - - if !p.input_mut().eat(&P::Token::COMMA) { - p.expect_general_semi() - } else { - Ok(()) - } -} - -/// `tsIsStartOfConstructSignature` -fn is_ts_start_of_construct_signature<'a, P: Parser<'a>>(p: &mut P) -> bool { - debug_assert!(p.input().syntax().typescript()); - - p.bump(); - let cur = p.input().cur(); - cur.is_lparen() || cur.is_less() -} - -/// `tsParseDelimitedList` -fn parse_ts_delimited_list<'a, P: Parser<'a>, T, F>( - p: &mut P, - kind: ParsingContext, - mut parse_element: F, -) -> PResult> -where - F: FnMut(&mut P) -> PResult, -{ - parse_ts_delimited_list_inner(p, kind, |p| { - let start = p.input().cur_pos(); - Ok((start, parse_element(p)?)) - }) -} - -/// `tsParseUnionOrIntersectionType` -fn parse_ts_union_or_intersection_type<'a, P: Parser<'a>, F>( - p: &mut P, - kind: UnionOrIntersection, - mut parse_constituent_type: F, - operator: &P::Token, -) -> PResult> -where - F: FnMut(&mut P) -> PResult>, -{ - trace_cur!(p, parse_ts_union_or_intersection_type); - - debug_assert!(p.input().syntax().typescript()); - - let start = p.input().cur_pos(); // include the leading operator in the start - p.input_mut().eat(operator); - trace_cur!(p, parse_ts_union_or_intersection_type__first_type); - - let ty = parse_constituent_type(p)?; - trace_cur!(p, parse_ts_union_or_intersection_type__after_first); - - if p.input().is(operator) { - let mut types = vec![ty]; - - while p.input_mut().eat(operator) { - trace_cur!(p, parse_ts_union_or_intersection_type__constituent); - - types.push(parse_constituent_type(p)?); - } - - return Ok(Box::new(TsType::TsUnionOrIntersectionType(match kind { - UnionOrIntersection::Union => TsUnionOrIntersectionType::TsUnionType(TsUnionType { - span: p.span(start), - types, - }), - UnionOrIntersection::Intersection => { - TsUnionOrIntersectionType::TsIntersectionType(TsIntersectionType { - span: p.span(start), - types, - }) - } - }))); - } - Ok(ty) -} - -pub fn eat_any_ts_modifier<'a>(p: &mut impl Parser<'a>) -> PResult { - if p.syntax().typescript() - && { - let cur = p.input().cur(); - cur.is_public() || cur.is_protected() || cur.is_private() || cur.is_readonly() - } - && peek!(p).is_some_and(|t| t.is_word() || t.is_lbrace() || t.is_lbracket()) - { - let _ = parse_ts_modifier(p, &["public", "protected", "private", "readonly"], false); - Ok(true) - } else { - Ok(false) - } -} - -/// Parses a modifier matching one the given modifier names. -/// -/// `tsParseModifier` -pub fn parse_ts_modifier<'a, P: Parser<'a>>( - p: &mut P, - allowed_modifiers: &[&'static str], - stop_on_start_of_class_static_blocks: bool, -) -> PResult> { - debug_assert!(p.input().syntax().typescript()); - let pos = { - let cur = p.input().cur(); - let modifier = if cur.is_unknown_ident() { - cur.clone().take_unknown_ident_ref(p.input()).clone() - } else if cur.is_known_ident() { - cur.take_known_ident() - } else if cur.is_in() { - atom!("in") - } else if cur.is_const() { - atom!("const") - } else if cur.is_error() { - let err = p.input_mut().expect_error_token_and_bump(); - return Err(err); - } else if cur.is_eof() { - return Err(eof_error(p)); - } else { - return Ok(None); - }; - // TODO: compare atom rather than string. - allowed_modifiers - .iter() - .position(|s| **s == *modifier.as_str()) - }; - if let Some(pos) = pos { - if stop_on_start_of_class_static_blocks - && p.input().is(&P::Token::STATIC) - && peek!(p).is_some_and(|peek| peek.is_lbrace()) - { - return Ok(None); - } - if try_parse_ts_bool(p, |p| Ok(Some(ts_next_token_can_follow_modifier(p))))? { - return Ok(Some(allowed_modifiers[pos])); - } - } - Ok(None) -} - -fn parse_ts_bracketed_list<'a, P: Parser<'a>, T, F>( - p: &mut P, - kind: ParsingContext, - parse_element: F, - bracket: bool, - skip_first_token: bool, -) -> PResult> -where - F: FnMut(&mut P) -> PResult, -{ - debug_assert!(p.input().syntax().typescript()); - if !skip_first_token { - if bracket { - expect!(p, &P::Token::LBRACKET); - } else { - expect!(p, &P::Token::LESS); - } - } - let result = parse_ts_delimited_list(p, kind, parse_element)?; - if bracket { - expect!(p, &P::Token::RBRACKET); - } else { - expect!(p, &P::Token::GREATER); - } - Ok(result) -} - -/// `tsParseThisTypeNode` -pub fn parse_ts_this_type_node<'a, P: Parser<'a>>(p: &mut P) -> PResult { - debug_assert!(p.input().syntax().typescript()); - expect!(p, &P::Token::THIS); - Ok(TsThisType { - span: p.input().prev_span(), - }) -} - -/// `tsParseEntityName` -pub fn parse_ts_entity_name<'a, P: Parser<'a>>( - p: &mut P, - allow_reserved_words: bool, -) -> PResult { - debug_assert!(p.input().syntax().typescript()); - trace_cur!(p, parse_ts_entity_name); - let start = p.input().cur_pos(); - let init = parse_ident_name(p)?; - if &*init.sym == "void" { - let dot_start = p.input().cur_pos(); - let dot_span = p.span(dot_start); - p.emit_err(dot_span, SyntaxError::TS1005) - } - let mut entity = TsEntityName::Ident(init.into()); - while p.input_mut().eat(&P::Token::DOT) { - let dot_start = p.input().cur_pos(); - let cur = p.input().cur(); - if !cur.is_hash() && !cur.is_word() { - p.emit_err( - Span::new_with_checked(dot_start, dot_start), - SyntaxError::TS1003, - ); - return Ok(entity); - } - let left = entity; - let right = if allow_reserved_words { - parse_ident_name(p)? - } else { - parse_ident(p, false, false)?.into() - }; - let span = p.span(start); - entity = TsEntityName::TsQualifiedName(Box::new(TsQualifiedName { span, left, right })); - } - Ok(entity) -} - -pub fn ts_look_ahead<'a, P: Parser<'a>, T, F>(p: &mut P, op: F) -> T -where - F: FnOnce(&mut P) -> T, -{ - debug_assert!(p.input().syntax().typescript()); - let checkpoint = p.checkpoint_save(); - p.set_ctx(p.ctx() | Context::IgnoreError); - let ret = op(p); - p.checkpoint_load(checkpoint); - ret -} - -/// `tsParseTypeArguments` -pub fn parse_ts_type_args<'a, P: Parser<'a>>(p: &mut P) -> PResult> { - trace_cur!(p, parse_ts_type_args); - debug_assert!(p.input().syntax().typescript()); - - let start = p.input().cur_pos(); - let params = p.in_type(|p| { - // Temporarily remove a JSX parsing context, which makes us scan different - // tokens. - p.ts_in_no_context(|p| { - if p.input().is(&P::Token::LSHIFT) { - p.input_mut().cut_lshift(); - } else { - expect!(p, &P::Token::LESS); - } - parse_ts_delimited_list(p, ParsingContext::TypeParametersOrArguments, |p| { - trace_cur!(p, parse_ts_type_args__arg); - - parse_ts_type(p) - }) - }) - })?; - // This reads the next token after the `>` too, so do this in the enclosing - // context. But be sure not to parse a regex in the jsx expression - // ` />`, so set exprAllowed = false - p.input_mut().set_expr_allowed(false); - p.expect_without_advance(&P::Token::GREATER)?; - let span = Span::new_with_checked(start, p.input().cur_span().hi); - - // Report grammar error for empty type argument list like `I<>`. - if params.is_empty() { - p.emit_err(span, SyntaxError::EmptyTypeArgumentList); - } - - Ok(Box::new(TsTypeParamInstantiation { span, params })) -} - -/// `tsParseTypeReference` -pub fn parse_ts_type_ref<'a, P: Parser<'a>>(p: &mut P) -> PResult { - trace_cur!(p, parse_ts_type_ref); - debug_assert!(p.input().syntax().typescript()); - - let start = p.input().cur_pos(); - - let has_modifier = eat_any_ts_modifier(p)?; - - let type_name = parse_ts_entity_name(p, /* allow_reserved_words */ true)?; - trace_cur!(p, parse_ts_type_ref__type_args); - let type_params = if !p.input().had_line_break_before_cur() - && (p.input().is(&P::Token::LESS) || p.input().is(&P::Token::LSHIFT)) - { - let ret = p.do_outside_of_context(Context::ShouldNotLexLtOrGtAsType, parse_ts_type_args)?; - p.assert_and_bump(&P::Token::GREATER); - Some(ret) - } else { - None - }; - - if has_modifier { - p.emit_err(p.span(start), SyntaxError::TS2369); - } - - Ok(TsTypeRef { - span: p.span(start), - type_name, - type_params, - }) -} - -#[cfg_attr( - all(debug_assertions, feature = "tracing-spans"), - tracing::instrument(level = "debug", skip_all) -)] -pub fn parse_ts_type_ann<'a, P: Parser<'a>>( - p: &mut P, - eat_colon: bool, - start: BytePos, -) -> PResult> { - trace_cur!(p, parse_ts_type_ann); - - debug_assert!(p.input().syntax().typescript()); - - p.in_type(|p| { - if eat_colon { - p.assert_and_bump(&P::Token::COLON); - } - - trace_cur!(p, parse_ts_type_ann__after_colon); - - let type_ann = parse_ts_type(p)?; - - Ok(Box::new(TsTypeAnn { - span: p.span(start), - type_ann, - })) - }) -} - -/// `tsParseThisTypePredicate` -pub fn parse_ts_this_type_predicate<'a, P: Parser<'a>>( - p: &mut P, - start: BytePos, - has_asserts_keyword: bool, - lhs: TsThisType, -) -> PResult { - debug_assert!(p.input().syntax().typescript()); - - let param_name = TsThisTypeOrIdent::TsThisType(lhs); - let type_ann = if p.input_mut().eat(&P::Token::IS) { - let cur_pos = p.input().cur_pos(); - Some(parse_ts_type_ann( - p, // eat_colon - false, cur_pos, - )?) - } else { - None - }; - - Ok(TsTypePredicate { - span: p.span(start), - asserts: has_asserts_keyword, - param_name, - type_ann, - }) -} - -/// `tsEatThenParseType` -fn eat_then_parse_ts_type<'a, P: Parser<'a>>( - p: &mut P, - token_to_eat: &P::Token, -) -> PResult>> { - if !cfg!(feature = "typescript") { - return Ok(Default::default()); - } - - p.in_type(|p| { - if !p.input_mut().eat(token_to_eat) { - return Ok(None); - } - - parse_ts_type(p).map(Some) - }) -} - -/// `tsExpectThenParseType` -fn expect_then_parse_ts_type<'a, P: Parser<'a>>( - p: &mut P, - token: &P::Token, - token_str: &'static str, -) -> PResult> { - debug_assert!(p.input().syntax().typescript()); - - p.in_type(|p| { - if !p.input_mut().eat(token) { - let got = format!("{:?}", p.input().cur()); - syntax_error!( - p, - p.input().cur_span(), - SyntaxError::Unexpected { - got, - expected: token_str - } - ); - } - - parse_ts_type(p) - }) -} - -/// `tsParseMappedTypeParameter` -fn parse_ts_mapped_type_param<'a, P: Parser<'a>>(p: &mut P) -> PResult { - debug_assert!(p.input().syntax().typescript()); - - let start = p.input().cur_pos(); - let name = parse_ident_name(p)?; - let constraint = Some(expect_then_parse_ts_type(p, &P::Token::IN, "in")?); - - Ok(TsTypeParam { - span: p.span(start), - name: name.into(), - is_in: false, - is_out: false, - is_const: false, - constraint, - default: None, - }) -} - -/// `tsParseTypeParameter` -fn parse_ts_type_param<'a, P: Parser<'a>>( - p: &mut P, - permit_in_out: bool, - permit_const: bool, -) -> PResult { - debug_assert!(p.input().syntax().typescript()); - - let mut is_in = false; - let mut is_out = false; - let mut is_const = false; - - let start = p.input().cur_pos(); - - while let Some(modifer) = parse_ts_modifier( - p, - &[ - "public", - "private", - "protected", - "readonly", - "abstract", - "const", - "override", - "in", - "out", - ], - false, - )? { - match modifer { - "const" => { - is_const = true; - if !permit_const { - p.emit_err(p.input().prev_span(), SyntaxError::TS1277(atom!("const"))); - } - } - "in" => { - if !permit_in_out { - p.emit_err(p.input().prev_span(), SyntaxError::TS1274(atom!("in"))); - } else if is_in { - p.emit_err(p.input().prev_span(), SyntaxError::TS1030(atom!("in"))); - } else if is_out { - p.emit_err( - p.input().prev_span(), - SyntaxError::TS1029(atom!("in"), atom!("out")), - ); - } - is_in = true; - } - "out" => { - if !permit_in_out { - p.emit_err(p.input().prev_span(), SyntaxError::TS1274(atom!("out"))); - } else if is_out { - p.emit_err(p.input().prev_span(), SyntaxError::TS1030(atom!("out"))); - } - is_out = true; - } - other => p.emit_err(p.input().prev_span(), SyntaxError::TS1273(other.into())), - }; - } - - let name = p.in_type(parse_ident_name)?.into(); - let constraint = eat_then_parse_ts_type(p, &P::Token::EXTENDS)?; - let default = eat_then_parse_ts_type(p, &P::Token::EQUAL)?; - - Ok(TsTypeParam { - span: p.span(start), - name, - is_in, - is_out, - is_const, - constraint, - default, - }) -} - -/// `tsParseTypeParameter` -pub fn parse_ts_type_params<'a, P: Parser<'a>>( - p: &mut P, - permit_in_out: bool, - permit_const: bool, -) -> PResult> { - p.in_type(|p| { - p.ts_in_no_context(|p| { - let start = p.input().cur_pos(); - let cur = p.input().cur(); - if !cur.is_less() && !cur.is_jsx_tag_start() { - unexpected!(p, "< (jsx tag start)") - } - p.bump(); - - let params = parse_ts_bracketed_list( - p, - ParsingContext::TypeParametersOrArguments, - |p| parse_ts_type_param(p, permit_in_out, permit_const), // bracket - false, - // skip_first_token - true, - )?; - - Ok(Box::new(TsTypeParamDecl { - span: p.span(start), - params, - })) - }) - }) -} - -/// `tsTryParseTypeParameters` -pub fn try_parse_ts_type_params<'a, P: Parser<'a>>( - p: &mut P, - permit_in_out: bool, - permit_const: bool, -) -> PResult>> { - if !cfg!(feature = "typescript") { - return Ok(None); - } - - if p.input().cur().is_less() { - return parse_ts_type_params(p, permit_in_out, permit_const).map(Some); - } - - Ok(None) -} - -/// `tsParseTypeOrTypePredicateAnnotation` -pub fn parse_ts_type_or_type_predicate_ann<'a, P: Parser<'a>>( - p: &mut P, - return_token: &P::Token, -) -> PResult> { - debug_assert!(p.input().syntax().typescript()); - - p.in_type(|p| { - let return_token_start = p.input().cur_pos(); - if !p.input_mut().eat(return_token) { - let cur = format!("{:?}", p.input().cur()); - let span = p.input().cur_span(); - syntax_error!( - p, - span, - SyntaxError::Expected(format!("{return_token:?}"), cur) - ) - } - - let type_pred_start = p.input().cur_pos(); - let has_type_pred_asserts = p.input().cur().is_asserts() && { - let ctx = p.ctx(); - peek!(p).is_some_and(|peek| { - if peek.is_word() { - !peek.is_reserved(ctx) - } else { - false - } - }) - }; - - if has_type_pred_asserts { - p.assert_and_bump(&P::Token::ASSERTS); - } - - let has_type_pred_is = p.is_ident_ref() - && peek!(p).is_some_and(|peek| peek.is_is()) - && !p.input_mut().has_linebreak_between_cur_and_peeked(); - let is_type_predicate = has_type_pred_asserts || has_type_pred_is; - if !is_type_predicate { - return parse_ts_type_ann( - p, - // eat_colon - false, - return_token_start, - ); - } - - let type_pred_var = parse_ident_name(p)?; - let type_ann = if has_type_pred_is { - p.assert_and_bump(&P::Token::IS); - let pos = p.input().cur_pos(); - Some(parse_ts_type_ann( - p, // eat_colon - false, pos, - )?) - } else { - None - }; - - let node = Box::new(TsType::TsTypePredicate(TsTypePredicate { - span: p.span(type_pred_start), - asserts: has_type_pred_asserts, - param_name: TsThisTypeOrIdent::Ident(type_pred_var.into()), - type_ann, - })); - - Ok(Box::new(TsTypeAnn { - span: p.span(return_token_start), - type_ann: node, - })) - }) -} - -fn is_start_of_expr<'a>(p: &mut impl Parser<'a>) -> bool { - is_start_of_left_hand_side_expr(p) || { - let cur = p.input().cur(); - cur.is_plus() - || cur.is_minus() - || cur.is_tilde() - || cur.is_bang() - || cur.is_delete() - || cur.is_typeof() - || cur.is_void() - || cur.is_plus_plus() - || cur.is_minus_minus() - || cur.is_less() - || cur.is_await() - || cur.is_yield() - || (cur.is_hash() && peek!(p).is_some_and(|peek| peek.is_word())) - } -} - -#[cfg_attr( - all(debug_assertions, feature = "tracing-spans"), - tracing::instrument(level = "debug", skip_all) -)] -pub(super) fn try_parse_ts_type_args<'a, P: Parser<'a>>( - p: &mut P, -) -> Option> { - trace_cur!(p, try_parse_ts_type_args); - debug_assert!(p.input().syntax().typescript()); - - try_parse_ts(p, |p| { - let type_args = parse_ts_type_args(p)?; - p.assert_and_bump(&P::Token::GREATER); - let cur = p.input().cur(); - if cur.is_less() // invalid syntax - || cur.is_greater() || cur.is_equal() || cur.is_rshift() || cur.is_greater_eq() || cur.is_plus() || cur.is_minus() // becomes relational expression - || cur.is_lparen() || cur.is_no_substitution_template_literal() || cur.is_template_head() || cur.is_backquote() - // these should be type - // arguments in function - // call or template, not - // instantiation - // expression - { - Ok(None) - } else if p.input().had_line_break_before_cur() - || p.input().cur().is_bin_op() - || !is_start_of_expr(p) - { - Ok(Some(type_args)) - } else { - Ok(None) - } - }) -} - -/// `tsTryParseType` -fn try_parse_ts_type<'a, P: Parser<'a>>(p: &mut P) -> PResult>> { - if !cfg!(feature = "typescript") { - return Ok(None); - } - - eat_then_parse_ts_type(p, &P::Token::COLON) -} - -/// `tsTryParseTypeAnnotation` -#[cfg_attr( - all(debug_assertions, feature = "tracing-spans"), - tracing::instrument(level = "debug", skip_all) -)] -pub fn try_parse_ts_type_ann<'a, P: Parser<'a>>(p: &mut P) -> PResult>> { - if !cfg!(feature = "typescript") { - return Ok(None); - } - - if p.input().is(&P::Token::COLON) { - let pos = p.cur_pos(); - return parse_ts_type_ann(p, /* eat_colon */ true, pos).map(Some); - } - - Ok(None) -} - -/// `tsNextThenParseType` -pub(super) fn next_then_parse_ts_type<'a, P: Parser<'a>>(p: &mut P) -> PResult> { - debug_assert!(p.input().syntax().typescript()); - - let result = p.in_type(|p| { - p.bump(); - parse_ts_type(p) - }); - - if !p.ctx().contains(Context::InType) && { - let cur = p.input().cur(); - cur.is_less() || cur.is_greater() - } { - p.input_mut().merge_lt_gt(); - } - - result -} - -/// `tsParseEnumMember` -fn parse_ts_enum_member<'a, P: Parser<'a>>(p: &mut P) -> PResult { - debug_assert!(p.input().syntax().typescript()); - - let start = p.cur_pos(); - // TypeScript allows computed property names with literal expressions in enums. - // Non-literal computed properties (like ["a" + "b"]) are rejected. - // We normalize literal computed properties (["\t"] → "\t") to keep AST simple. - // See https://github.com/swc-project/swc/issues/11160 - let cur = p.input().cur(); - let id = if cur.is_str() { - TsEnumMemberId::Str(parse_str_lit(p)) - } else if cur.is_num() { - let (value, raw) = p.input_mut().expect_number_token_and_bump(); - let mut new_raw = String::new(); - - new_raw.push('"'); - new_raw.push_str(raw.as_str()); - new_raw.push('"'); - - let span = p.span(start); - - // Recover from error - p.emit_err(span, SyntaxError::TS2452); - - TsEnumMemberId::Str(Str { - span, - value: value.to_string().into(), - raw: Some(new_raw.into()), - }) - } else if cur.is_lbracket() { - p.assert_and_bump(&P::Token::LBRACKET); - let expr = p.parse_expr()?; - p.assert_and_bump(&P::Token::RBRACKET); - let bracket_span = p.span(start); - - match *expr { - Expr::Lit(Lit::Str(str_lit)) => { - // String literal: ["\t"] → "\t" - TsEnumMemberId::Str(str_lit) - } - Expr::Tpl(mut tpl) if tpl.exprs.is_empty() => { - // Template literal without substitution: [`hello`] → "hello" - - let tpl = mem::take(tpl.quasis.first_mut().unwrap()); - - let span = tpl.span; - let value = tpl.cooked.unwrap(); - - TsEnumMemberId::Str(Str { - span, - value, - raw: None, - }) - } - _ => { - // Non-literal expression: report error - p.emit_err(bracket_span, SyntaxError::TS1164); - TsEnumMemberId::Ident(Ident::new_no_ctxt(atom!(""), bracket_span)) - } - } - } else if cur.is_error() { - let err = p.input_mut().expect_error_token_and_bump(); - return Err(err); - } else { - parse_ident_name(p) - .map(Ident::from) - .map(TsEnumMemberId::from)? - }; - - let init = if p.input_mut().eat(&P::Token::EQUAL) { - Some(parse_assignment_expr(p)?) - } else if p.input().cur().is_comma() || p.input().cur().is_rbrace() { - None - } else { - let start = p.cur_pos(); - p.bump(); - p.input_mut().store(P::Token::COMMA); - p.emit_err(Span::new_with_checked(start, start), SyntaxError::TS1005); - None - }; - - Ok(TsEnumMember { - span: p.span(start), - id, - init, - }) -} - -/// `tsParseEnumDeclaration` -pub fn parse_ts_enum_decl<'a, P: Parser<'a>>( - p: &mut P, - start: BytePos, - is_const: bool, -) -> PResult> { - debug_assert!(p.input().syntax().typescript()); - - let id = parse_ident_name(p)?; - expect!(p, &P::Token::LBRACE); - let members = parse_ts_delimited_list(p, ParsingContext::EnumMembers, parse_ts_enum_member)?; - expect!(p, &P::Token::RBRACE); - - Ok(Box::new(TsEnumDecl { - span: p.span(start), - declare: false, - is_const, - id: id.into(), - members, - })) -} - -/// `tsTryParseTypeOrTypePredicateAnnotation` -/// -/// Used for parsing return types. -pub fn try_parse_ts_type_or_type_predicate_ann<'a, P: Parser<'a>>( - p: &mut P, -) -> PResult>> { - if !cfg!(feature = "typescript") { - return Ok(None); - } - - if p.input().is(&P::Token::COLON) { - parse_ts_type_or_type_predicate_ann(p, &P::Token::COLON).map(Some) - } else { - Ok(None) - } -} - -/// `tsParseTemplateLiteralType` -fn parse_ts_tpl_lit_type<'a, P: Parser<'a>>(p: &mut P) -> PResult { - debug_assert!(p.input().syntax().typescript()); - - let start = p.cur_pos(); - - p.assert_and_bump(&P::Token::BACKQUOTE); - - let (types, quasis) = parse_ts_tpl_type_elements(p)?; - - expect!(p, &P::Token::BACKQUOTE); - - Ok(TsTplLitType { - span: p.span(start), - types, - quasis, - }) -} - -fn parse_ts_tpl_type_elements<'a, P: Parser<'a>>( - p: &mut P, -) -> PResult<(Vec>, Vec)> { - if !cfg!(feature = "typescript") { - return Ok(Default::default()); - } - - trace_cur!(p, parse_tpl_elements); - - let mut types = Vec::new(); - - let cur_elem = p.parse_tpl_element(false)?; - let mut is_tail = cur_elem.tail; - let mut quasis = vec![cur_elem]; - - while !is_tail { - expect!(p, &P::Token::DOLLAR_LBRACE); - types.push(parse_ts_type(p)?); - expect!(p, &P::Token::RBRACE); - let elem = p.parse_tpl_element(false)?; - is_tail = elem.tail; - quasis.push(elem); - } - - Ok((types, quasis)) -} - -/// `tsParseLiteralTypeNode` -fn parse_ts_lit_type_node<'a, P: Parser<'a>>(p: &mut P) -> PResult { - debug_assert!(p.input().syntax().typescript()); - - let start = p.cur_pos(); - - let lit = if p.input().is(&P::Token::BACKQUOTE) { - let tpl = parse_ts_tpl_lit_type(p)?; - TsLit::Tpl(tpl) - } else { - match parse_lit(p)? { - Lit::BigInt(n) => TsLit::BigInt(n), - Lit::Bool(n) => TsLit::Bool(n), - Lit::Num(n) => TsLit::Number(n), - Lit::Str(n) => TsLit::Str(n), - _ => unreachable!(), - } - }; - - Ok(TsLitType { - span: p.span(start), - lit, - }) -} - -/// `tsParseHeritageClause` -pub fn parse_ts_heritage_clause<'a>(p: &mut impl Parser<'a>) -> PResult> { - debug_assert!(p.input().syntax().typescript()); - - parse_ts_delimited_list( - p, - ParsingContext::HeritageClauseElement, - parse_ts_heritage_clause_element, - ) -} - -fn parse_ts_heritage_clause_element<'a, P: Parser<'a>>(p: &mut P) -> PResult { - debug_assert!(p.input().syntax().typescript()); - - let start = p.cur_pos(); - // Note: TS uses parseLeftHandSideExpressionOrHigher, - // then has grammar errors later if it's not an EntityName. - - let ident = parse_ident_name(p)?.into(); - let expr = parse_subscripts(p, Callee::Expr(ident), true, true)?; - if !matches!( - &*expr, - Expr::Ident(..) | Expr::Member(..) | Expr::TsInstantiation(..) - ) { - p.emit_err(p.span(start), SyntaxError::TS2499); - } - - match *expr { - Expr::TsInstantiation(v) => Ok(TsExprWithTypeArgs { - span: v.span, - expr: v.expr, - type_args: Some(v.type_args), - }), - _ => { - let type_args = if p.input().is(&P::Token::LESS) { - let ret = parse_ts_type_args(p)?; - p.assert_and_bump(&P::Token::GREATER); - Some(ret) - } else { - None - }; - - Ok(TsExprWithTypeArgs { - span: p.span(start), - expr, - type_args, - }) - } - } -} - -/// `tsSkipParameterStart` -fn skip_ts_parameter_start<'a, P: Parser<'a>>(p: &mut P) -> PResult { - debug_assert!(p.input().syntax().typescript()); - - let _ = eat_any_ts_modifier(p)?; - - let cur = p.input().cur(); - - if cur.is_void() { - Ok(false) - } else if cur.is_word() || cur.is_this() { - p.bump(); - Ok(true) - } else if (cur.is_lbrace() || cur.is_lbracket()) && parse_binding_pat_or_ident(p, false).is_ok() - { - Ok(true) - } else { - Ok(false) - } -} - -/// `tsIsUnambiguouslyStartOfFunctionType` -fn is_ts_unambiguously_start_of_fn_type<'a, P: Parser<'a>>(p: &mut P) -> PResult { - debug_assert!(p.input().syntax().typescript()); - - p.assert_and_bump(&P::Token::LPAREN); - - let cur = p.input().cur(); - if cur.is_rparen() || cur.is_dotdotdot() { - // ( ) - // ( ... - return Ok(true); - } - if skip_ts_parameter_start(p)? { - let cur = p.input().cur(); - if cur.is_colon() || cur.is_comma() || cur.is_equal() || cur.is_question() { - // ( xxx : - // ( xxx , - // ( xxx ? - // ( xxx = - return Ok(true); - } - if p.input_mut().eat(&P::Token::RPAREN) && p.input().cur().is_arrow() { - // ( xxx ) => - return Ok(true); - } - } - Ok(false) -} - -fn is_ts_start_of_fn_type<'a, P: Parser<'a>>(p: &mut P) -> bool { - debug_assert!(p.input().syntax().typescript()); - - if p.input().cur().is_less() { - return true; - } - - p.input().cur().is_lparen() - && ts_look_ahead(p, is_ts_unambiguously_start_of_fn_type).unwrap_or_default() -} - -/// `tsIsUnambiguouslyIndexSignature` -fn is_ts_unambiguously_index_signature<'a, P: Parser<'a>>(p: &mut P) -> bool { - debug_assert!(p.input().syntax().typescript()); - - // Note: babel's comment is wrong - p.assert_and_bump(&P::Token::LBRACKET); // Skip '[' - - // ',' is for error recovery - p.eat_ident_ref() && { - let cur = p.input().cur(); - cur.is_comma() || cur.is_colon() - } -} - -/// `tsTryParseIndexSignature` -pub fn try_parse_ts_index_signature<'a, P: Parser<'a>>( - p: &mut P, - index_signature_start: BytePos, - readonly: bool, - is_static: bool, -) -> PResult> { - if !cfg!(feature = "typescript") { - return Ok(Default::default()); - } - - if !(p.input().cur().is_lbracket() && ts_look_ahead(p, is_ts_unambiguously_index_signature)) { - return Ok(None); - } - - expect!(p, &P::Token::LBRACKET); - - let ident_start = p.cur_pos(); - let mut id = parse_ident_name(p).map(BindingIdent::from)?; - let type_ann_start = p.cur_pos(); - - if p.input_mut().eat(&P::Token::COMMA) { - p.emit_err(id.span, SyntaxError::TS1096); - } else { - expect!(p, &P::Token::COLON); - } - - let type_ann = parse_ts_type_ann(p, /* eat_colon */ false, type_ann_start)?; - id.span = p.span(ident_start); - id.type_ann = Some(type_ann); - - expect!(p, &P::Token::RBRACKET); - - let params = vec![TsFnParam::Ident(id)]; - - let ty = try_parse_ts_type_ann(p)?; - let type_ann = ty; - - parse_ts_type_member_semicolon(p)?; - - Ok(Some(TsIndexSignature { - span: p.span(index_signature_start), - readonly, - is_static, - params, - type_ann, - })) -} - -/// `tsIsExternalModuleReference` -fn is_ts_external_module_ref<'a, P: Parser<'a>>(p: &mut P) -> bool { - debug_assert!(p.input().syntax().typescript()); - p.input().is(&P::Token::REQUIRE) && peek!(p).is_some_and(|t| t.is_lparen()) -} - -/// `tsParseModuleReference` -fn parse_ts_module_ref<'a>(p: &mut impl Parser<'a>) -> PResult { - debug_assert!(p.input().syntax().typescript()); - - if is_ts_external_module_ref(p) { - parse_ts_external_module_ref(p).map(From::from) - } else { - parse_ts_entity_name(p, /* allow_reserved_words */ false).map(From::from) - } -} - -/// `tsParseExternalModuleReference` -fn parse_ts_external_module_ref<'a, P: Parser<'a>>(p: &mut P) -> PResult { - debug_assert!(p.input().syntax().typescript()); - - let start = p.cur_pos(); - expect!(p, &P::Token::REQUIRE); - expect!(p, &P::Token::LPAREN); - let cur = p.input().cur(); - if cur.is_error() { - let err = p.input_mut().expect_error_token_and_bump(); - return Err(err); - } else if !cur.is_str() { - unexpected!(p, "a string literal") - } - let expr = parse_str_lit(p); - expect!(p, &P::Token::RPAREN); - Ok(TsExternalModuleRef { - span: p.span(start), - expr, - }) -} - -/// `tsParseImportEqualsDeclaration` -pub fn parse_ts_import_equals_decl<'a, P: Parser<'a>>( - p: &mut P, - start: BytePos, - id: Ident, - is_export: bool, - is_type_only: bool, -) -> PResult> { - debug_assert!(p.input().syntax().typescript()); - - expect!(p, &P::Token::EQUAL); - let module_ref = parse_ts_module_ref(p)?; - p.expect_general_semi()?; - - Ok(Box::new(TsImportEqualsDecl { - span: p.span(start), - id, - is_export, - is_type_only, - module_ref, - })) -} - -/// `tsParseBindingListForSignature` -/// -/// Eats ')` at the end but does not eat `(` at start. -fn parse_ts_binding_list_for_signature<'a, P: Parser<'a>>(p: &mut P) -> PResult> { - if !cfg!(feature = "typescript") { - return Ok(Default::default()); - } - - debug_assert!(p.input().syntax().typescript()); - - let params = parse_formal_params(p)?; - let mut list = Vec::with_capacity(4); - - for param in params { - let item = match param.pat { - Pat::Ident(pat) => TsFnParam::Ident(pat), - Pat::Array(pat) => TsFnParam::Array(pat), - Pat::Object(pat) => TsFnParam::Object(pat), - Pat::Rest(pat) => TsFnParam::Rest(pat), - _ => unexpected!( - p, - "an identifier, [ for an array pattern, { for an object patter or ... for a rest \ - pattern" - ), - }; - list.push(item); - } - expect!(p, &P::Token::RPAREN); - Ok(list) -} - -/// `tsIsStartOfMappedType` -pub fn is_ts_start_of_mapped_type<'a, P: Parser<'a>>(p: &mut P) -> bool { - debug_assert!(p.input().syntax().typescript()); - - p.bump(); - if p.input_mut().eat(&P::Token::PLUS) || p.input_mut().eat(&P::Token::MINUS) { - return p.input().is(&P::Token::READONLY); - } - - p.input_mut().eat(&P::Token::READONLY); - - if !p.input().is(&P::Token::LBRACKET) { - return false; - } - p.bump(); - if !p.is_ident_ref() { - return false; - } - p.bump(); - - p.input().is(&P::Token::IN) -} - -/// `tsParseSignatureMember` -fn parse_ts_signature_member<'a, P: Parser<'a>>( - p: &mut P, - kind: SignatureParsingMode, -) -> PResult> { - debug_assert!(p.input().syntax().typescript()); - - let start = p.cur_pos(); - - if kind == SignatureParsingMode::TSConstructSignatureDeclaration { - expect!(p, &P::Token::NEW); - } - - // ----- inlined p.tsFillSignature(tt.colon, node); - let type_params = try_parse_ts_type_params(p, false, true)?; - expect!(p, &P::Token::LPAREN); - let params = parse_ts_binding_list_for_signature(p)?; - let type_ann = if p.input().is(&P::Token::COLON) { - Some(parse_ts_type_or_type_predicate_ann(p, &P::Token::COLON)?) - } else { - None - }; - // ----- - - parse_ts_type_member_semicolon(p)?; - - match kind { - SignatureParsingMode::TSCallSignatureDeclaration => Ok(Either::Left(TsCallSignatureDecl { - span: p.span(start), - params, - type_ann, - type_params, - })), - SignatureParsingMode::TSConstructSignatureDeclaration => { - Ok(Either::Right(TsConstructSignatureDecl { - span: p.span(start), - params, - type_ann, - type_params, - })) - } - } -} - -fn try_parse_ts_tuple_element_name<'a, P: Parser<'a>>(p: &mut P) -> Option { - if !cfg!(feature = "typescript") { - return Default::default(); - } - - try_parse_ts(p, |p| { - let start = p.cur_pos(); - - let rest = if p.input_mut().eat(&P::Token::DOTDOTDOT) { - Some(p.input().prev_span()) - } else { - None - }; - - let mut ident = parse_ident_name(p).map(Ident::from)?; - if p.input_mut().eat(&P::Token::QUESTION) { - ident.optional = true; - ident.span = ident.span.with_hi(p.input().prev_span().hi); - } - expect!(p, &P::Token::COLON); - - Ok(Some(if let Some(dot3_token) = rest { - RestPat { - span: p.span(start), - dot3_token, - arg: ident.into(), - type_ann: None, - } - .into() - } else { - ident.into() - })) - }) -} - -/// `tsParseTupleElementType` -fn parse_ts_tuple_element_type<'a, P: Parser<'a>>(p: &mut P) -> PResult { - debug_assert!(p.input().syntax().typescript()); - - // parses `...TsType[]` - let start = p.cur_pos(); - - let label = try_parse_ts_tuple_element_name(p); - - if p.input_mut().eat(&P::Token::DOTDOTDOT) { - let type_ann = parse_ts_type(p)?; - return Ok(TsTupleElement { - span: p.span(start), - label, - ty: Box::new(TsType::TsRestType(TsRestType { - span: p.span(start), - type_ann, - })), - }); - } - - let ty = parse_ts_type(p)?; - // parses `TsType?` - if p.input_mut().eat(&P::Token::QUESTION) { - let type_ann = ty; - return Ok(TsTupleElement { - span: p.span(start), - label, - ty: Box::new(TsType::TsOptionalType(TsOptionalType { - span: p.span(start), - type_ann, - })), - }); - } - - Ok(TsTupleElement { - span: p.span(start), - label, - ty, - }) -} - -/// `tsParseTupleType` -pub fn parse_ts_tuple_type<'a, P: Parser<'a>>(p: &mut P) -> PResult { - debug_assert!(p.input().syntax().typescript()); - - let start = p.cur_pos(); - let elems = parse_ts_bracketed_list( - p, - ParsingContext::TupleElementTypes, - parse_ts_tuple_element_type, - /* bracket */ true, - /* skipFirstToken */ false, - )?; - - // Validate the elementTypes to ensure: - // No mandatory elements may follow optional elements - // If there's a rest element, it must be at the end of the tuple - - let mut seen_optional_element = false; - - for elem in elems.iter() { - match *elem.ty { - TsType::TsRestType(..) => {} - TsType::TsOptionalType(..) => { - seen_optional_element = true; - } - _ if seen_optional_element => { - syntax_error!(p, p.span(start), SyntaxError::TsRequiredAfterOptional) - } - _ => {} - } - } - - Ok(TsTupleType { - span: p.span(start), - elem_types: elems, - }) -} - -/// `tsParseMappedType` -pub fn parse_ts_mapped_type<'a, P: Parser<'a>>(p: &mut P) -> PResult { - debug_assert!(p.input().syntax().typescript()); - - let start = p.cur_pos(); - expect!(p, &P::Token::LBRACE); - let mut readonly = None; - let cur = p.input().cur(); - if cur.is_plus() || cur.is_minus() { - readonly = Some(if cur.is_plus() { - TruePlusMinus::Plus - } else { - TruePlusMinus::Minus - }); - p.bump(); - expect!(p, &P::Token::READONLY) - } else if p.input_mut().eat(&P::Token::READONLY) { - readonly = Some(TruePlusMinus::True); - } - - expect!(p, &P::Token::LBRACKET); - let type_param = parse_ts_mapped_type_param(p)?; - let name_type = if p.input_mut().eat(&P::Token::AS) { - Some(parse_ts_type(p)?) - } else { - None - }; - expect!(p, &P::Token::RBRACKET); - - let mut optional = None; - let cur = p.input().cur(); - if cur.is_plus() || cur.is_minus() { - optional = Some(if cur.is_plus() { - TruePlusMinus::Plus - } else { - TruePlusMinus::Minus - }); - p.bump(); // +, - - expect!(p, &P::Token::QUESTION); - } else if p.input_mut().eat(&P::Token::QUESTION) { - optional = Some(TruePlusMinus::True); - } - - let type_ann = try_parse_ts_type(p)?; - p.expect_general_semi()?; - expect!(p, &P::Token::RBRACE); - - Ok(TsMappedType { - span: p.span(start), - readonly, - optional, - type_param, - name_type, - type_ann, - }) -} - -/// `tsParseParenthesizedType` -pub fn parse_ts_parenthesized_type<'a, P: Parser<'a>>(p: &mut P) -> PResult { - debug_assert!(p.input().syntax().typescript()); - trace_cur!(p, parse_ts_parenthesized_type); - - let start = p.cur_pos(); - expect!(p, &P::Token::LPAREN); - let type_ann = parse_ts_type(p)?; - expect!(p, &P::Token::RPAREN); - Ok(TsParenthesizedType { - span: p.span(start), - type_ann, - }) -} - -/// `tsParseTypeAliasDeclaration` -pub fn parse_ts_type_alias_decl<'a, P: Parser<'a>>( - p: &mut P, - start: BytePos, -) -> PResult> { - debug_assert!(p.input().syntax().typescript()); - - let id = parse_ident_name(p)?; - let type_params = try_parse_ts_type_params(p, true, false)?; - let type_ann = expect_then_parse_ts_type(p, &P::Token::EQUAL, "=")?; - p.expect_general_semi()?; - Ok(Box::new(TsTypeAliasDecl { - declare: false, - span: p.span(start), - id: id.into(), - type_params, - type_ann, - })) -} - -/// `tsParseFunctionOrConstructorType` -fn parse_ts_fn_or_constructor_type<'a, P: Parser<'a>>( - p: &mut P, - is_fn_type: bool, -) -> PResult { - trace_cur!(p, parse_ts_fn_or_constructor_type); - - debug_assert!(p.input().syntax().typescript()); - - let start = p.cur_pos(); - let is_abstract = if !is_fn_type { - p.input_mut().eat(&P::Token::ABSTRACT) - } else { - false - }; - if !is_fn_type { - expect!(p, &P::Token::NEW); - } - - // ----- inlined `p.tsFillSignature(tt.arrow, node)` - let type_params = try_parse_ts_type_params(p, false, true)?; - expect!(p, &P::Token::LPAREN); - let params = parse_ts_binding_list_for_signature(p)?; - let type_ann = parse_ts_type_or_type_predicate_ann(p, &P::Token::ARROW)?; - // ----- end - - Ok(if is_fn_type { - TsFnOrConstructorType::TsFnType(TsFnType { - span: p.span(start), - type_params, - params, - type_ann, - }) - } else { - TsFnOrConstructorType::TsConstructorType(TsConstructorType { - span: p.span(start), - type_params, - params, - type_ann, - is_abstract, - }) - }) -} - -/// `tsParseUnionTypeOrHigher` -fn parse_ts_union_type_or_higher<'a, P: Parser<'a>>(p: &mut P) -> PResult> { - trace_cur!(p, parse_ts_union_type_or_higher); - debug_assert!(p.input().syntax().typescript()); - - parse_ts_union_or_intersection_type( - p, - UnionOrIntersection::Union, - parse_ts_intersection_type_or_higher, - &P::Token::BIT_OR, - ) -} - -/// `tsParseIntersectionTypeOrHigher` -fn parse_ts_intersection_type_or_higher<'a, P: Parser<'a>>(p: &mut P) -> PResult> { - trace_cur!(p, parse_ts_intersection_type_or_higher); - - debug_assert!(p.input().syntax().typescript()); - - parse_ts_union_or_intersection_type( - p, - UnionOrIntersection::Intersection, - parse_ts_type_operator_or_higher, - &P::Token::BIT_AND, - ) -} - -/// `tsParseTypeOperatorOrHigher` -fn parse_ts_type_operator_or_higher<'a, P: Parser<'a>>(p: &mut P) -> PResult> { - trace_cur!(p, parse_ts_type_operator_or_higher); - debug_assert!(p.input().syntax().typescript()); - - let operator = if p.input().is(&P::Token::KEYOF) { - Some(TsTypeOperatorOp::KeyOf) - } else if p.input().is(&P::Token::UNIQUE) { - Some(TsTypeOperatorOp::Unique) - } else if p.input().is(&P::Token::READONLY) { - Some(TsTypeOperatorOp::ReadOnly) - } else { - None - }; - - match operator { - Some(operator) => parse_ts_type_operator(p, operator) - .map(TsType::from) - .map(Box::new), - None => { - trace_cur!(p, parse_ts_type_operator_or_higher__not_operator); - - if p.input().is(&P::Token::INFER) { - parse_ts_infer_type(p).map(TsType::from).map(Box::new) - } else { - let readonly = parse_ts_modifier(p, &["readonly"], false)?.is_some(); - parse_ts_array_type_or_higher(p, readonly) - } - } - } -} - -/// `tsParseTypeOperator` -fn parse_ts_type_operator<'a, P: Parser<'a>>( - p: &mut P, - op: TsTypeOperatorOp, -) -> PResult { - debug_assert!(p.input().syntax().typescript()); - - let start = p.cur_pos(); - match op { - TsTypeOperatorOp::Unique => expect!(p, &P::Token::UNIQUE), - TsTypeOperatorOp::KeyOf => expect!(p, &P::Token::KEYOF), - TsTypeOperatorOp::ReadOnly => expect!(p, &P::Token::READONLY), - #[cfg(swc_ast_unknown)] - _ => unreachable!(), - } - - let type_ann = parse_ts_type_operator_or_higher(p)?; - Ok(TsTypeOperator { - span: p.span(start), - op, - type_ann, - }) -} - -/// `tsParseInferType` -fn parse_ts_infer_type<'a, P: Parser<'a>>(p: &mut P) -> PResult { - debug_assert!(p.input().syntax().typescript()); - - let start = p.cur_pos(); - expect!(p, &P::Token::INFER); - let type_param_name = parse_ident_name(p)?; - let constraint = try_parse_ts(p, |p| { - expect!(p, &P::Token::EXTENDS); - let constraint = parse_ts_non_conditional_type(p); - if p.ctx().contains(Context::DisallowConditionalTypes) || !p.input().is(&P::Token::QUESTION) - { - constraint.map(Some) - } else { - Ok(None) - } - }); - let type_param = TsTypeParam { - span: type_param_name.span(), - name: type_param_name.into(), - is_in: false, - is_out: false, - is_const: false, - constraint, - default: None, - }; - Ok(TsInferType { - span: p.span(start), - type_param, - }) -} - -/// `tsParseNonConditionalType` -fn parse_ts_non_conditional_type<'a, P: Parser<'a>>(p: &mut P) -> PResult> { - trace_cur!(p, parse_ts_non_conditional_type); - - debug_assert!(p.input().syntax().typescript()); - - if is_ts_start_of_fn_type(p) { - return parse_ts_fn_or_constructor_type(p, true) - .map(TsType::from) - .map(Box::new); - } - if (p.input().is(&P::Token::ABSTRACT) && peek!(p).is_some_and(|cur| cur.is_new())) - || p.input().is(&P::Token::NEW) - { - // As in `new () => Date` - return parse_ts_fn_or_constructor_type(p, false) - .map(TsType::from) - .map(Box::new); - } - - parse_ts_union_type_or_higher(p) -} - -/// `tsParseArrayTypeOrHigher` -fn parse_ts_array_type_or_higher<'a, P: Parser<'a>>( - p: &mut P, - readonly: bool, -) -> PResult> { - trace_cur!(p, parse_ts_array_type_or_higher); - debug_assert!(p.input().syntax().typescript()); - - let mut ty = parse_ts_non_array_type(p)?; - - while !p.input().had_line_break_before_cur() && p.input_mut().eat(&P::Token::LBRACKET) { - if p.input_mut().eat(&P::Token::RBRACKET) { - ty = Box::new(TsType::TsArrayType(TsArrayType { - span: p.span(ty.span_lo()), - elem_type: ty, - })); - } else { - let index_type = parse_ts_type(p)?; - expect!(p, &P::Token::RBRACKET); - ty = Box::new(TsType::TsIndexedAccessType(TsIndexedAccessType { - span: p.span(ty.span_lo()), - readonly, - obj_type: ty, - index_type, - })) - } - } - - Ok(ty) -} - -/// Be sure to be in a type context before calling p. -/// -/// `tsParseType` -pub fn parse_ts_type<'a, P: Parser<'a>>(p: &mut P) -> PResult> { - trace_cur!(p, parse_ts_type); - - debug_assert!(p.input().syntax().typescript()); - - // Need to set `state.inType` so that we don't parse JSX in a type context. - debug_assert!(p.ctx().contains(Context::InType)); - - let start = p.cur_pos(); - - p.do_outside_of_context(Context::DisallowConditionalTypes, |p| { - let ty = parse_ts_non_conditional_type(p)?; - if p.input().had_line_break_before_cur() || !p.input_mut().eat(&P::Token::EXTENDS) { - return Ok(ty); - } - - let check_type = ty; - let extends_type = p.do_inside_of_context( - Context::DisallowConditionalTypes, - parse_ts_non_conditional_type, - )?; - - expect!(p, &P::Token::QUESTION); - - let true_type = parse_ts_type(p)?; - - expect!(p, &P::Token::COLON); - - let false_type = parse_ts_type(p)?; - - Ok(Box::new(TsType::TsConditionalType(TsConditionalType { - span: p.span(start), - check_type, - extends_type, - true_type, - false_type, - }))) - }) -} - -/// `parsePropertyName` in babel. -/// -/// Returns `(computed, key)`. -fn parse_ts_property_name<'a, P: Parser<'a>>(p: &mut P) -> PResult<(bool, Box)> { - let (computed, key) = if p.input_mut().eat(&P::Token::LBRACKET) { - let key = parse_assignment_expr(p)?; - expect!(p, &P::Token::RBRACKET); - (true, key) - } else { - p.do_inside_of_context(Context::InPropertyName, |p| { - // We check if it's valid for it to be a private name when we push it. - let cur = p.input().cur(); - - let key = if cur.is_num() || cur.is_str() { - parse_new_expr(p) - } else if cur.is_error() { - let err = p.input_mut().expect_error_token_and_bump(); - return Err(err); - } else { - parse_maybe_private_name(p).map(|e| match e { - Either::Left(e) => { - p.emit_err(e.span(), SyntaxError::PrivateNameInInterface); - - e.into() - } - Either::Right(e) => e.into(), - }) - }; - key.map(|key| (false, key)) - })? - }; - - Ok((computed, key)) -} - -/// `tsParsePropertyOrMethodSignature` -fn parse_ts_property_or_method_signature<'a, P: Parser<'a>>( - p: &mut P, - start: BytePos, - readonly: bool, -) -> PResult> { - debug_assert!(p.input().syntax().typescript()); - - let (computed, key) = parse_ts_property_name(p)?; - - let optional = p.input_mut().eat(&P::Token::QUESTION); - - let cur = p.input().cur(); - if cur.is_lparen() || cur.is_less() { - if readonly { - syntax_error!(p, SyntaxError::ReadOnlyMethod) - } - - let type_params = try_parse_ts_type_params(p, false, true)?; - expect!(p, &P::Token::LPAREN); - let params = parse_ts_binding_list_for_signature(p)?; - let type_ann = if p.input().is(&P::Token::COLON) { - parse_ts_type_or_type_predicate_ann(p, &P::Token::COLON).map(Some)? - } else { - None - }; - // ----- - - parse_ts_type_member_semicolon(p)?; - Ok(Either::Right(TsMethodSignature { - span: p.span(start), - computed, - key, - optional, - type_params, - params, - type_ann, - })) - } else { - let type_ann = try_parse_ts_type_ann(p)?; - - parse_ts_type_member_semicolon(p)?; - Ok(Either::Left(TsPropertySignature { - span: p.span(start), - computed, - readonly, - key, - optional, - type_ann, - })) - } -} - -/// `tsParseTypeMember` -fn parse_ts_type_member<'a, P: Parser<'a>>(p: &mut P) -> PResult { - debug_assert!(p.input().syntax().typescript()); - - fn into_type_elem(e: Either) -> TsTypeElement { - match e { - Either::Left(e) => e.into(), - Either::Right(e) => e.into(), - } - } - let cur = p.input().cur(); - if cur.is_lparen() || cur.is_less() { - return parse_ts_signature_member(p, SignatureParsingMode::TSCallSignatureDeclaration) - .map(into_type_elem); - } - if p.input().is(&P::Token::NEW) && ts_look_ahead(p, is_ts_start_of_construct_signature) { - return parse_ts_signature_member(p, SignatureParsingMode::TSConstructSignatureDeclaration) - .map(into_type_elem); - } - // Instead of fullStart, we create a node here. - let start = p.cur_pos(); - let readonly = parse_ts_modifier(p, &["readonly"], false)?.is_some(); - - let idx = try_parse_ts_index_signature(p, start, readonly, false)?; - if let Some(idx) = idx { - return Ok(idx.into()); - } - - if let Some(v) = try_parse_ts(p, |p| { - let start = p.input().cur_pos(); - - if readonly { - syntax_error!(p, SyntaxError::GetterSetterCannotBeReadonly) - } - - let is_get = if p.input_mut().eat(&P::Token::GET) { - true - } else { - expect!(p, &P::Token::SET); - false - }; - - let (computed, key) = parse_ts_property_name(p)?; - - if is_get { - expect!(p, &P::Token::LPAREN); - expect!(p, &P::Token::RPAREN); - let type_ann = try_parse_ts_type_ann(p)?; - - parse_ts_type_member_semicolon(p)?; - - Ok(Some(TsTypeElement::TsGetterSignature(TsGetterSignature { - span: p.span(start), - key, - computed, - type_ann, - }))) - } else { - expect!(p, &P::Token::LPAREN); - let params = parse_ts_binding_list_for_signature(p)?; - if params.is_empty() { - syntax_error!(p, SyntaxError::SetterParamRequired) - } - let param = params.into_iter().next().unwrap(); - - parse_ts_type_member_semicolon(p)?; - - Ok(Some(TsTypeElement::TsSetterSignature(TsSetterSignature { - span: p.span(start), - key, - computed, - param, - }))) - } - }) { - return Ok(v); - } - - parse_ts_property_or_method_signature(p, start, readonly).map(|e| match e { - Either::Left(e) => e.into(), - Either::Right(e) => e.into(), - }) -} - -/// `tsParseObjectTypeMembers` -fn parse_ts_object_type_members<'a, P: Parser<'a>>(p: &mut P) -> PResult> { - debug_assert!(p.input().syntax().typescript()); - - expect!(p, &P::Token::LBRACE); - let members = parse_ts_list(p, ParsingContext::TypeMembers, |p| parse_ts_type_member(p))?; - expect!(p, &P::Token::RBRACE); - Ok(members) -} - -/// `tsParseTypeLiteral` -pub fn parse_ts_type_lit<'a>(p: &mut impl Parser<'a>) -> PResult { - debug_assert!(p.input().syntax().typescript()); - - let start = p.cur_pos(); - let members = parse_ts_object_type_members(p)?; - Ok(TsTypeLit { - span: p.span(start), - members, - }) -} - -/// `tsParseInterfaceDeclaration` -pub fn parse_ts_interface_decl<'a, P: Parser<'a>>( - p: &mut P, - start: BytePos, -) -> PResult> { - debug_assert!(p.input().syntax().typescript()); - - let id = parse_ident_name(p)?; - match &*id.sym { - "string" | "null" | "number" | "object" | "any" | "unknown" | "boolean" | "bigint" - | "symbol" | "void" | "never" | "intrinsic" => { - p.emit_err(id.span, SyntaxError::TS2427); - } - _ => {} - } - - let type_params = try_parse_ts_type_params(p, true, false)?; - - let extends = if p.input_mut().eat(&P::Token::EXTENDS) { - parse_ts_heritage_clause(p)? - } else { - Vec::new() - }; - - // Recover from - // - // interface I extends A extends B {} - if p.input().is(&P::Token::EXTENDS) { - p.emit_err(p.input().cur_span(), SyntaxError::TS1172); - - while !p.input().cur().is_eof() && !p.input().is(&P::Token::LBRACE) { - p.bump(); - } - } - - let body_start = p.cur_pos(); - let body = p.in_type(parse_ts_object_type_members)?; - let body = TsInterfaceBody { - span: p.span(body_start), - body, - }; - Ok(Box::new(TsInterfaceDecl { - span: p.span(start), - declare: false, - id: id.into(), - type_params, - extends, - body, - })) -} - -/// `tsParseTypeAssertion` -pub fn parse_ts_type_assertion<'a, P: Parser<'a>>( - p: &mut P, - start: BytePos, -) -> PResult { - debug_assert!(p.input().syntax().typescript()); - - if p.input().syntax().disallow_ambiguous_jsx_like() { - p.emit_err(p.span(start), SyntaxError::ReservedTypeAssertion); - } - - // Not actually necessary to set state.inType because we never reach here if JSX - // plugin is enabled, but need `tsInType` to satisfy the assertion in - // `tsParseType`. - let type_ann = p.in_type(parse_ts_type)?; - expect!(p, &P::Token::GREATER); - let expr = p.parse_unary_expr()?; - Ok(TsTypeAssertion { - span: p.span(start), - type_ann, - expr, - }) -} - -/// `tsParseImportType` -fn parse_ts_import_type<'a, P: Parser<'a>>(p: &mut P) -> PResult { - let start = p.cur_pos(); - p.assert_and_bump(&P::Token::IMPORT); - - expect!(p, &P::Token::LPAREN); - - let cur = p.input().cur(); - - let arg = if cur.is_str() { - parse_str_lit(p) - } else if cur.is_error() { - let err = p.input_mut().expect_error_token_and_bump(); - return Err(err); - } else { - let arg_span = p.input().cur_span(); - p.bump(); - p.emit_err(arg_span, SyntaxError::TS1141); - Str { - span: arg_span, - value: Wtf8Atom::default(), - raw: Some(atom!("\"\"")), - } - }; - - // the "assert" keyword is deprecated and this syntax is niche, so - // don't support it - let attributes = if p.input_mut().eat(&P::Token::COMMA) - && p.input().syntax().import_attributes() - && p.input().is(&P::Token::LBRACE) - { - Some(parse_ts_call_options(p)?) - } else { - None - }; - - expect!(p, &P::Token::RPAREN); - - let qualifier = if p.input_mut().eat(&P::Token::DOT) { - parse_ts_entity_name(p, false).map(Some)? - } else { - None - }; - - let type_args = if p.input().is(&P::Token::LESS) { - let ret = p.do_outside_of_context(Context::ShouldNotLexLtOrGtAsType, parse_ts_type_args)?; - p.assert_and_bump(&P::Token::GREATER); - Some(ret) - } else { - None - }; - - Ok(TsImportType { - span: p.span(start), - arg, - qualifier, - type_args, - attributes, - }) -} - -fn parse_ts_call_options<'a, P: Parser<'a>>(p: &mut P) -> PResult { - debug_assert!(p.input().syntax().typescript()); - let start = p.cur_pos(); - p.assert_and_bump(&P::Token::LBRACE); - - expect!(p, &P::Token::WITH); - expect!(p, &P::Token::COLON); - - let value = match parse_object_expr(p)? { - Expr::Object(v) => v, - _ => unreachable!(), - }; - p.input_mut().eat(&P::Token::COMMA); - expect!(p, &P::Token::RBRACE); - Ok(TsImportCallOptions { - span: p.span(start), - with: Box::new(value), - }) -} - -/// `tsParseTypeQuery` -fn parse_ts_type_query<'a, P: Parser<'a>>(p: &mut P) -> PResult { - debug_assert!(p.input().syntax().typescript()); - - let start = p.cur_pos(); - expect!(p, &P::Token::TYPEOF); - let expr_name = if p.input().is(&P::Token::IMPORT) { - parse_ts_import_type(p).map(From::from)? - } else { - parse_ts_entity_name( - p, // allow_reserved_word - true, - ) - .map(From::from)? - }; - - let type_args = if !p.input().had_line_break_before_cur() && p.input().is(&P::Token::LESS) { - let ret = p.do_outside_of_context(Context::ShouldNotLexLtOrGtAsType, parse_ts_type_args)?; - p.assert_and_bump(&P::Token::GREATER); - Some(ret) - } else { - None - }; - - Ok(TsTypeQuery { - span: p.span(start), - expr_name, - type_args, - }) -} - -/// `tsParseModuleBlock` -fn parse_ts_module_block<'a, P: Parser<'a>>(p: &mut P) -> PResult { - trace_cur!(p, parse_ts_module_block); - - debug_assert!(p.input().syntax().typescript()); - - let start = p.cur_pos(); - expect!(p, &P::Token::LBRACE); - let body = p.do_inside_of_context(Context::TsModuleBlock, |p| { - p.do_outside_of_context(Context::TopLevel, |p| { - parse_module_item_block_body(p, false, Some(&P::Token::RBRACE)) - }) - })?; - - Ok(TsModuleBlock { - span: p.span(start), - body, - }) -} - -/// `tsParseModuleOrNamespaceDeclaration` -fn parse_ts_module_or_ns_decl<'a, P: Parser<'a>>( - p: &mut P, - start: BytePos, - namespace: bool, -) -> PResult> { - debug_assert!(p.input().syntax().typescript()); - - let id = parse_ident_name(p)?; - let body: TsNamespaceBody = if p.input_mut().eat(&P::Token::DOT) { - let inner_start = p.cur_pos(); - let inner = parse_ts_module_or_ns_decl(p, inner_start, namespace)?; - let inner = TsNamespaceDecl { - span: inner.span, - id: match inner.id { - TsModuleName::Ident(i) => i, - _ => unreachable!(), - }, - body: Box::new(inner.body.unwrap()), - declare: inner.declare, - global: inner.global, - }; - inner.into() - } else { - parse_ts_module_block(p).map(From::from)? - }; - - Ok(Box::new(TsModuleDecl { - span: p.span(start), - declare: false, - id: TsModuleName::Ident(id.into()), - body: Some(body), - global: false, - namespace, - })) -} - -/// `tsParseAmbientExternalModuleDeclaration` -fn parse_ts_ambient_external_module_decl<'a, P: Parser<'a>>( - p: &mut P, - start: BytePos, -) -> PResult> { - debug_assert!(p.input().syntax().typescript()); - - let (global, id) = if p.input().is(&P::Token::GLOBAL) { - let id = parse_ident_name(p)?; - (true, TsModuleName::Ident(id.into())) - } else if p.input().cur().is_str() { - let id = TsModuleName::Str(parse_str_lit(p)); - (false, id) - } else { - unexpected!(p, "global or a string literal"); - }; - - let body = if p.input().is(&P::Token::LBRACE) { - Some(parse_ts_module_block(p).map(TsNamespaceBody::from)?) - } else { - p.expect_general_semi()?; - None - }; - - Ok(Box::new(TsModuleDecl { - span: p.span(start), - declare: false, - id, - global, - body, - namespace: false, - })) -} - -/// `tsParseNonArrayType` -fn parse_ts_non_array_type<'a, P: Parser<'a>>(p: &mut P) -> PResult> { - if !cfg!(feature = "typescript") { - unreachable!() - } - trace_cur!(p, parse_ts_non_array_type); - debug_assert!(p.input().syntax().typescript()); - - let start = p.cur_pos(); - - let cur = p.input().cur(); - if cur.is_known_ident() - || cur.is_unknown_ident() - || cur.is_void() - || cur.is_yield() - || cur.is_null() - || cur.is_await() - || cur.is_break() - { - if p.input().is(&P::Token::ASSERTS) && peek!(p).is_some_and(|peek| peek.is_this()) { - p.bump(); - let this_keyword = parse_ts_this_type_node(p)?; - return parse_ts_this_type_predicate(p, start, true, this_keyword) - .map(TsType::from) - .map(Box::new); - } - let kind = if p.input().is(&P::Token::VOID) { - Some(TsKeywordTypeKind::TsVoidKeyword) - } else if p.input().is(&P::Token::NULL) { - Some(TsKeywordTypeKind::TsNullKeyword) - } else if p.input().is(&P::Token::ANY) { - Some(TsKeywordTypeKind::TsAnyKeyword) - } else if p.input().is(&P::Token::BOOLEAN) { - Some(TsKeywordTypeKind::TsBooleanKeyword) - } else if p.input().is(&P::Token::BIGINT) { - Some(TsKeywordTypeKind::TsBigIntKeyword) - } else if p.input().is(&P::Token::NEVER) { - Some(TsKeywordTypeKind::TsNeverKeyword) - } else if p.input().is(&P::Token::NUMBER) { - Some(TsKeywordTypeKind::TsNumberKeyword) - } else if p.input().is(&P::Token::OBJECT) { - Some(TsKeywordTypeKind::TsObjectKeyword) - } else if p.input().is(&P::Token::STRING) { - Some(TsKeywordTypeKind::TsStringKeyword) - } else if p.input().is(&P::Token::SYMBOL) { - Some(TsKeywordTypeKind::TsSymbolKeyword) - } else if p.input().is(&P::Token::UNKNOWN) { - Some(TsKeywordTypeKind::TsUnknownKeyword) - } else if p.input().is(&P::Token::UNDEFINED) { - Some(TsKeywordTypeKind::TsUndefinedKeyword) - } else if p.input().is(&P::Token::INTRINSIC) { - Some(TsKeywordTypeKind::TsIntrinsicKeyword) - } else { - None - }; - - let peeked_is_dot = peek!(p).is_some_and(|cur| cur.is_dot()); - - match kind { - Some(kind) if !peeked_is_dot => { - p.bump(); - return Ok(Box::new(TsType::TsKeywordType(TsKeywordType { - span: p.span(start), - kind, - }))); - } - _ => { - return parse_ts_type_ref(p).map(TsType::from).map(Box::new); - } - } - } else if cur.is_bigint() - || cur.is_str() - || cur.is_num() - || cur.is_true() - || cur.is_false() - || cur.is_backquote() - { - return parse_ts_lit_type_node(p).map(TsType::from).map(Box::new); - } else if cur.is_no_substitution_template_literal() || cur.is_template_head() { - return p.parse_tagged_tpl_ty().map(TsType::from).map(Box::new); - } else if cur.is_minus() { - let start = p.cur_pos(); - - p.bump(); - - let cur = p.input().cur(); - if !(cur.is_num() || cur.is_bigint()) { - unexpected!(p, "numeric literal or bigint literal") - } - - let lit = parse_lit(p)?; - let lit = match lit { - Lit::Num(Number { span, value, raw }) => { - let mut new_raw = String::from("-"); - - match raw { - Some(raw) => { - new_raw.push_str(&raw); - } - _ => { - write!(new_raw, "{value}").unwrap(); - } - }; - - TsLit::Number(Number { - span, - value: -value, - raw: Some(new_raw.into()), - }) - } - Lit::BigInt(BigInt { span, value, raw }) => { - let mut new_raw = String::from("-"); - - match raw { - Some(raw) => { - new_raw.push_str(&raw); - } - _ => { - write!(new_raw, "{value}").unwrap(); - } - }; - - TsLit::BigInt(BigInt { - span, - value: Box::new(-*value), - raw: Some(new_raw.into()), - }) - } - _ => unreachable!(), - }; - - return Ok(Box::new(TsType::TsLitType(TsLitType { - span: p.span(start), - lit, - }))); - } else if cur.is_import() { - return parse_ts_import_type(p).map(TsType::from).map(Box::new); - } else if cur.is_this() { - let start = p.cur_pos(); - let this_keyword = parse_ts_this_type_node(p)?; - return if !p.input().had_line_break_before_cur() && p.input().is(&P::Token::IS) { - parse_ts_this_type_predicate(p, start, false, this_keyword) - .map(TsType::from) - .map(Box::new) - } else { - Ok(Box::new(TsType::TsThisType(this_keyword))) - }; - } else if cur.is_typeof() { - return parse_ts_type_query(p).map(TsType::from).map(Box::new); - } else if cur.is_lbrace() { - return if ts_look_ahead(p, is_ts_start_of_mapped_type) { - parse_ts_mapped_type(p).map(TsType::from).map(Box::new) - } else { - parse_ts_type_lit(p).map(TsType::from).map(Box::new) - }; - } else if cur.is_lbracket() { - return parse_ts_tuple_type(p).map(TsType::from).map(Box::new); - } else if cur.is_lparen() { - return parse_ts_parenthesized_type(p) - .map(TsType::from) - .map(Box::new); - } - - // switch (p.state.type) { - // } - - unexpected!( - p, - "an identifier, void, yield, null, await, break, a string literal, a numeric literal, \ - true, false, `, -, import, this, typeof, {, [, (" - ) -} - -/// `tsParseExpressionStatement` -pub fn parse_ts_expr_stmt<'a, P: Parser<'a>>( - p: &mut P, - decorators: Vec, - expr: Ident, -) -> PResult> { - if !cfg!(feature = "typescript") { - return Ok(Default::default()); - } - - let start = expr.span_lo(); - - match &*expr.sym { - "declare" => { - let decl = try_parse_ts_declare(p, start, decorators)?; - if let Some(decl) = decl { - Ok(Some(make_decl_declare(decl))) - } else { - Ok(None) - } - } - "global" => { - // `global { }` (with no `declare`) may appear inside an ambient module - // declaration. - // Would like to use tsParseAmbientExternalModuleDeclaration here, but already - // ran past "global". - if p.input().is(&P::Token::LBRACE) { - let global = true; - let id = TsModuleName::Ident(expr); - let body = parse_ts_module_block(p) - .map(TsNamespaceBody::from) - .map(Some)?; - Ok(Some( - TsModuleDecl { - span: p.span(start), - global, - declare: false, - namespace: false, - id, - body, - } - .into(), - )) - } else { - Ok(None) - } - } - _ => parse_ts_decl(p, start, decorators, expr.sym, /* next */ false), - } -} - -/// `tsTryParseDeclare` -pub fn try_parse_ts_declare<'a, P: Parser<'a>>( - p: &mut P, - start: BytePos, - decorators: Vec, -) -> PResult> { - if !p.syntax().typescript() { - return Ok(None); - } - - if p.ctx() - .contains(Context::InDeclare | Context::TsModuleBlock) - { - let span_of_declare = p.span(start); - p.emit_err(span_of_declare, SyntaxError::TS1038); - } - - let declare_start = start; - p.do_inside_of_context(Context::InDeclare, |p| { - if p.input().is(&P::Token::FUNCTION) { - return parse_fn_decl(p, decorators) - .map(|decl| match decl { - Decl::Fn(f) => FnDecl { - declare: true, - function: Box::new(Function { - span: Span { - lo: declare_start, - ..f.function.span - }, - ..*f.function - }), - ..f - } - .into(), - _ => decl, - }) - .map(Some); - } - - if p.input().is(&P::Token::CLASS) { - return parse_class_decl(p, start, start, decorators, false) - .map(|decl| match decl { - Decl::Class(c) => ClassDecl { - declare: true, - class: Box::new(Class { - span: Span { - lo: declare_start, - ..c.class.span - }, - ..*c.class - }), - ..c - } - .into(), - _ => decl, - }) - .map(Some); - } - - if p.input().is(&P::Token::CONST) && peek!(p).is_some_and(|peek| peek.is_enum()) { - p.assert_and_bump(&P::Token::CONST); - p.assert_and_bump(&P::Token::ENUM); - - return parse_ts_enum_decl(p, start, /* is_const */ true) - .map(|decl| TsEnumDecl { - declare: true, - span: Span { - lo: declare_start, - ..decl.span - }, - ..*decl - }) - .map(Box::new) - .map(From::from) - .map(Some); - } - - let cur = p.input().cur(); - if cur.is_const() || cur.is_var() || cur.is_let() { - return parse_var_stmt(p, false) - .map(|decl| VarDecl { - declare: true, - span: Span { - lo: declare_start, - ..decl.span - }, - ..*decl - }) - .map(Box::new) - .map(From::from) - .map(Some); - } - - if p.input().is(&P::Token::GLOBAL) { - return parse_ts_ambient_external_module_decl(p, start) - .map(Decl::from) - .map(make_decl_declare) - .map(Some); - } else if p.input().cur().is_word() { - let value = p - .input_mut() - .cur() - .clone() - .take_word(p.input_mut()) - .unwrap(); - return parse_ts_decl(p, start, decorators, value, /* next */ true) - .map(|v| v.map(make_decl_declare)); - } - - Ok(None) - }) -} - -/// `tsTryParseExportDeclaration` -/// -/// Note: this won't be called unless the keyword is allowed in -/// `shouldParseExportDeclaration`. -pub fn try_parse_ts_export_decl<'a, P: Parser<'a>>( - p: &mut P, - decorators: Vec, - value: Atom, -) -> Option { - if !cfg!(feature = "typescript") { - return None; - } - - try_parse_ts(p, |p| { - let start = p.cur_pos(); - let opt = parse_ts_decl(p, start, decorators, value, true)?; - Ok(opt) - }) -} - -/// Common to tsTryParseDeclare, tsTryParseExportDeclaration, and -/// tsParseExpressionStatement. -/// -/// `tsParseDeclaration` -fn parse_ts_decl<'a, P: Parser<'a>>( - p: &mut P, - start: BytePos, - decorators: Vec, - value: Atom, - next: bool, -) -> PResult> { - if !cfg!(feature = "typescript") { - return Ok(Default::default()); - } - - match &*value { - "abstract" - if next - || (p.input().is(&P::Token::CLASS) && !p.input().had_line_break_before_cur()) => - { - if next { - p.bump(); - } - return Ok(Some(parse_class_decl(p, start, start, decorators, true)?)); - } - - "enum" if next || p.is_ident_ref() => { - if next { - p.bump(); - } - return parse_ts_enum_decl(p, start, /* is_const */ false) - .map(From::from) - .map(Some); - } - - "interface" if next || p.is_ident_ref() => { - if next { - p.bump(); - } - - return parse_ts_interface_decl(p, start).map(From::from).map(Some); - } - - "module" if !p.input().had_line_break_before_cur() => { - if next { - p.bump(); - } - - let cur = p.input().cur(); - if cur.is_str() { - return parse_ts_ambient_external_module_decl(p, start) - .map(From::from) - .map(Some); - } else if cur.is_error() { - let err = p.input_mut().expect_error_token_and_bump(); - return Err(err); - } else if cur.is_eof() { - return Err(eof_error(p)); - } else if next || p.is_ident_ref() { - return parse_ts_module_or_ns_decl(p, start, false) - .map(From::from) - .map(Some); - } - } - - "namespace" if next || p.is_ident_ref() => { - if next { - p.bump(); - } - return parse_ts_module_or_ns_decl(p, start, true) - .map(From::from) - .map(Some); - } - - "type" if next || (!p.input().had_line_break_before_cur() && p.is_ident_ref()) => { - if next { - p.bump(); - } - return parse_ts_type_alias_decl(p, start).map(From::from).map(Some); - } - - _ => {} - } - - Ok(None) -} - -/// `tsTryParseGenericAsyncArrowFunction` -pub fn try_parse_ts_generic_async_arrow_fn<'a, P: Parser<'a>>( - p: &mut P, - start: BytePos, -) -> PResult> { - if !cfg!(feature = "typescript") { - return Ok(Default::default()); - } - - let cur = p.input().cur(); - let res = if cur.is_less() || cur.is_jsx_tag_start() { - try_parse_ts(p, |p| { - let type_params = parse_ts_type_params(p, false, false)?; - - // In TSX mode, type parameters that could be mistaken for JSX - // (single param without constraint and no trailing comma) are not - // allowed. - if p.input().syntax().jsx() - && !p.input().syntax().flow() - && type_params.params.len() == 1 - { - let single_param = &type_params.params[0]; - let has_trailing_comma = type_params.span.hi.0 - single_param.span.hi.0 > 1; - let dominated_by_jsx = single_param.constraint.is_none() - && single_param.default.is_none() - && !has_trailing_comma; - - if dominated_by_jsx { - return Ok(None); - } - } - - // Don't use overloaded parseFunctionParams which would look for "<" again. - expect!(p, &P::Token::LPAREN); - let params: Vec = parse_formal_params(p)?.into_iter().map(|p| p.pat).collect(); - expect!(p, &P::Token::RPAREN); - let return_type = try_parse_ts_type_or_type_predicate_ann(p)?; - expect!(p, &P::Token::ARROW); - - Ok(Some((type_params, params, return_type))) - }) - } else { - None - }; - - let (type_params, params, return_type) = match res { - Some(v) => v, - None => return Ok(None), - }; - - p.do_inside_of_context(Context::InAsync, |p| { - p.do_outside_of_context(Context::InGenerator, |p| { - let is_generator = false; - let is_async = true; - let body = parse_fn_block_or_expr_body( - p, - true, - false, - true, - params.is_simple_parameter_list(), - )?; - Ok(Some(ArrowExpr { - span: p.span(start), - body, - is_async, - is_generator, - type_params: Some(type_params), - params, - return_type, - ..Default::default() - })) - }) - }) -} diff --git a/crates/swc_ecma_lexer/src/common/parser/util.rs b/crates/swc_ecma_lexer/src/common/parser/util.rs deleted file mode 100644 index e48c6dcbcdac..000000000000 --- a/crates/swc_ecma_lexer/src/common/parser/util.rs +++ /dev/null @@ -1,91 +0,0 @@ -use swc_atoms::{atom, Atom}; -use swc_common::{Span, Spanned}; -use swc_ecma_ast::{ - BindingIdent, BlockStmt, Decl, Expr, Ident, IdentName, JSXElementName, JSXMemberExpr, - JSXNamespacedName, JSXObject, Key, Param, Pat, PropName, Str, -}; - -pub fn unwrap_ts_non_null(mut expr: &Expr) -> &Expr { - while let Expr::TsNonNull(ts_non_null) = expr { - expr = &ts_non_null.expr; - } - expr -} - -pub fn is_not_this(p: &Param) -> bool { - !matches!( - &p.pat, - Pat::Ident(BindingIdent { - id: Ident{ sym: this, .. }, - .. - }) if atom!("this").eq(this) - ) -} - -pub fn has_use_strict(block: &BlockStmt) -> Option { - block - .stmts - .iter() - .take_while(|s| s.can_precede_directive()) - .find_map(|s| { - if s.is_use_strict() { - Some(s.span()) - } else { - None - } - }) -} - -pub fn is_constructor(key: &Key) -> bool { - if let Key::Public(PropName::Ident(IdentName { sym, .. })) = key { - sym.eq("constructor") - } else if let Key::Public(PropName::Str(Str { value, .. })) = key { - value.eq("constructor") - } else { - false - } -} - -pub fn get_qualified_jsx_name(name: &JSXElementName) -> Atom { - fn get_qualified_obj_name(obj: &JSXObject) -> Atom { - match *obj { - JSXObject::Ident(ref i) => i.sym.clone(), - JSXObject::JSXMemberExpr(ref member) => format!( - "{}.{}", - get_qualified_obj_name(&member.obj), - member.prop.sym - ) - .into(), - #[cfg(swc_ast_unknown)] - _ => unreachable!(), - } - } - match *name { - JSXElementName::Ident(ref i) => i.sym.clone(), - JSXElementName::JSXNamespacedName(JSXNamespacedName { - ref ns, ref name, .. - }) => format!("{}:{}", ns.sym, name.sym).into(), - JSXElementName::JSXMemberExpr(JSXMemberExpr { - ref obj, ref prop, .. - }) => format!("{}.{}", get_qualified_obj_name(obj), prop.sym).into(), - #[cfg(swc_ast_unknown)] - _ => unreachable!(), - } -} - -/// Mark as declare -pub fn make_decl_declare(mut decl: Decl) -> Decl { - match decl { - Decl::Class(ref mut c) => c.declare = true, - Decl::Fn(ref mut f) => f.declare = true, - Decl::Var(ref mut v) => v.declare = true, - Decl::TsInterface(ref mut i) => i.declare = true, - Decl::TsTypeAlias(ref mut a) => a.declare = true, - Decl::TsEnum(ref mut e) => e.declare = true, - Decl::TsModule(ref mut m) => m.declare = true, - Decl::Using(..) => unreachable!("Using is not a valid declaration for `declare` keyword"), - #[cfg(swc_ast_unknown)] - _ => unreachable!(), - } - decl -} diff --git a/crates/swc_ecma_lexer/src/common/parser/verifier.rs b/crates/swc_ecma_lexer/src/common/parser/verifier.rs deleted file mode 100644 index a935bbce1a2e..000000000000 --- a/crates/swc_ecma_lexer/src/common/parser/verifier.rs +++ /dev/null @@ -1,24 +0,0 @@ -use swc_common::{Span, Spanned}; -use swc_ecma_ast::{AssignProp, Expr}; -use swc_ecma_visit::{noop_visit_type, Visit, VisitWith}; - -use crate::error::SyntaxError; - -pub struct Verifier { - pub errors: Vec<(Span, SyntaxError)>, -} - -impl Visit for Verifier { - noop_visit_type!(); - - fn visit_assign_prop(&mut self, p: &AssignProp) { - self.errors.push((p.span(), SyntaxError::AssignProperty)); - } - - fn visit_expr(&mut self, e: &Expr) { - match *e { - Expr::Fn(..) | Expr::Arrow(..) => {} - _ => e.visit_children_with(self), - } - } -} diff --git a/crates/swc_ecma_lexer/src/common/syntax.rs b/crates/swc_ecma_lexer/src/common/syntax.rs deleted file mode 100644 index 4b65660ae7ca..000000000000 --- a/crates/swc_ecma_lexer/src/common/syntax.rs +++ /dev/null @@ -1 +0,0 @@ -pub use swc_ecma_parser::{EsSyntax, Syntax, SyntaxFlags, TsSyntax}; diff --git a/crates/swc_ecma_lexer/src/error.rs b/crates/swc_ecma_lexer/src/error.rs deleted file mode 100644 index 67e11a09d5c7..000000000000 --- a/crates/swc_ecma_lexer/src/error.rs +++ /dev/null @@ -1 +0,0 @@ -pub use swc_ecma_parser::error::{Error, SyntaxError}; diff --git a/crates/swc_ecma_lexer/src/input.rs b/crates/swc_ecma_lexer/src/input.rs deleted file mode 100644 index 154dc9622bbe..000000000000 --- a/crates/swc_ecma_lexer/src/input.rs +++ /dev/null @@ -1,493 +0,0 @@ -use std::{cell::RefCell, mem, mem::take, rc::Rc}; - -use lexer::TokenContexts; -use swc_common::{BytePos, Span}; -use swc_ecma_ast::EsVersion; - -use crate::{ - common::{ - input::Tokens, - lexer::token::TokenFactory, - parser::token_and_span::TokenAndSpan as TokenAndSpanTrait, - syntax::{Syntax, SyntaxFlags}, - }, - error::Error, - lexer::{self}, - token::*, - Context, -}; - -#[derive(Clone)] -pub struct TokensInput { - iter: as IntoIterator>::IntoIter, - ctx: Context, - syntax: SyntaxFlags, - start_pos: BytePos, - target: EsVersion, - token_ctx: TokenContexts, - errors: Rc>>, - module_errors: Rc>>, -} - -impl TokensInput { - pub fn new(tokens: Vec, ctx: Context, syntax: Syntax, target: EsVersion) -> Self { - let start_pos = tokens.first().map(|t| t.span.lo).unwrap_or(BytePos(0)); - - TokensInput { - iter: tokens.into_iter(), - ctx, - syntax: syntax.into_flags(), - start_pos, - target, - token_ctx: Default::default(), - errors: Default::default(), - module_errors: Default::default(), - } - } -} - -impl Iterator for TokensInput { - type Item = TokenAndSpan; - - fn next(&mut self) -> Option { - self.iter.next() - } -} - -impl Tokens for TokensInput { - type Checkpoint = Self; - - fn checkpoint_save(&self) -> Self::Checkpoint { - self.clone() - } - - fn checkpoint_load(&mut self, checkpoint: Self::Checkpoint) { - *self = checkpoint; - } - - fn set_ctx(&mut self, ctx: Context) { - if ctx.contains(Context::Module) && !self.module_errors.borrow().is_empty() { - let mut module_errors = self.module_errors.borrow_mut(); - self.errors.borrow_mut().append(&mut *module_errors); - } - self.ctx = ctx; - } - - fn ctx_mut(&mut self) -> &mut Context { - &mut self.ctx - } - - #[inline(always)] - fn ctx(&self) -> Context { - self.ctx - } - - #[inline(always)] - fn syntax(&self) -> SyntaxFlags { - self.syntax - } - - #[inline(always)] - fn target(&self) -> EsVersion { - self.target - } - - #[inline(always)] - fn start_pos(&self) -> BytePos { - self.start_pos - } - - #[inline(always)] - fn set_expr_allowed(&mut self, _: bool) {} - - #[inline(always)] - fn set_next_regexp(&mut self, _: Option) {} - - #[inline(always)] - fn token_context(&self) -> &TokenContexts { - &self.token_ctx - } - - #[inline(always)] - fn token_context_mut(&mut self) -> &mut TokenContexts { - &mut self.token_ctx - } - - #[inline(always)] - fn set_token_context(&mut self, c: TokenContexts) { - self.token_ctx = c; - } - - #[inline(always)] - fn add_error(&mut self, error: Error) { - self.errors.borrow_mut().push(error); - } - - #[inline(always)] - fn add_module_mode_error(&mut self, error: Error) { - if self.ctx.contains(Context::Module) { - self.add_error(error); - return; - } - self.module_errors.borrow_mut().push(error); - } - - #[inline(always)] - fn take_errors(&mut self) -> Vec { - take(&mut self.errors.borrow_mut()) - } - - #[inline(always)] - fn take_script_module_errors(&mut self) -> Vec { - take(&mut self.module_errors.borrow_mut()) - } - - fn end_pos(&self) -> BytePos { - self.iter - .as_slice() - .last() - .map(|t| t.span.hi) - .unwrap_or(self.start_pos) - } - - #[inline] - fn update_token_flags(&mut self, _: impl FnOnce(&mut lexer::TokenFlags)) { - // TODO: Implement this method if needed. - } - - fn token_flags(&self) -> lexer::TokenFlags { - Default::default() - } -} - -/// Note: Lexer need access to parser's context to lex correctly. -#[derive(Debug)] -pub struct Capturing> { - inner: I, - captured: Rc>>, -} - -impl> Clone for Capturing { - fn clone(&self) -> Self { - Capturing { - inner: self.inner.clone(), - captured: self.captured.clone(), - } - } -} - -impl> Capturing { - pub fn new(input: I) -> Self { - Capturing { - inner: input, - captured: Default::default(), - } - } - - pub fn tokens(&self) -> Rc>> { - self.captured.clone() - } - - /// Take captured tokens - pub fn take(&mut self) -> Vec { - mem::take(&mut *self.captured.borrow_mut()) - } -} - -impl> Iterator for Capturing { - type Item = TokenAndSpan; - - fn next(&mut self) -> Option { - let next = self.inner.next(); - - match next { - Some(ts) => { - let mut v = self.captured.borrow_mut(); - - // remove tokens that could change due to backtracing - while let Some(last) = v.last() { - if last.span.lo >= ts.span.lo { - v.pop(); - } else { - break; - } - } - - v.push(ts.clone()); - - Some(ts) - } - None => None, - } - } -} - -impl> Tokens for Capturing { - type Checkpoint = I::Checkpoint; - - fn checkpoint_save(&self) -> Self::Checkpoint { - self.inner.checkpoint_save() - } - - fn checkpoint_load(&mut self, checkpoint: Self::Checkpoint) { - self.inner.checkpoint_load(checkpoint); - } - - #[inline(always)] - fn set_ctx(&mut self, ctx: Context) { - self.inner.set_ctx(ctx) - } - - fn ctx_mut(&mut self) -> &mut Context { - self.inner.ctx_mut() - } - - #[inline(always)] - fn ctx(&self) -> Context { - self.inner.ctx() - } - - #[inline(always)] - fn syntax(&self) -> SyntaxFlags { - self.inner.syntax() - } - - #[inline(always)] - fn target(&self) -> EsVersion { - self.inner.target() - } - - #[inline(always)] - fn start_pos(&self) -> BytePos { - self.inner.start_pos() - } - - #[inline(always)] - fn set_expr_allowed(&mut self, allow: bool) { - self.inner.set_expr_allowed(allow) - } - - #[inline(always)] - fn set_next_regexp(&mut self, start: Option) { - self.inner.set_next_regexp(start); - } - - #[inline(always)] - fn token_context(&self) -> &TokenContexts { - self.inner.token_context() - } - - #[inline(always)] - fn token_context_mut(&mut self) -> &mut TokenContexts { - self.inner.token_context_mut() - } - - #[inline(always)] - fn set_token_context(&mut self, c: TokenContexts) { - self.inner.set_token_context(c) - } - - #[inline(always)] - fn add_error(&mut self, error: Error) { - self.inner.add_error(error); - } - - #[inline(always)] - fn add_module_mode_error(&mut self, error: Error) { - self.inner.add_module_mode_error(error) - } - - #[inline(always)] - fn take_errors(&mut self) -> Vec { - self.inner.take_errors() - } - - fn take_script_module_errors(&mut self) -> Vec { - self.inner.take_script_module_errors() - } - - fn end_pos(&self) -> BytePos { - self.inner.end_pos() - } - - #[inline] - fn update_token_flags(&mut self, _: impl FnOnce(&mut lexer::TokenFlags)) { - // TODO: Implement this method if needed. - } - - fn token_flags(&self) -> lexer::TokenFlags { - Default::default() - } -} - -/// This struct is responsible for managing current token and peeked token. -#[derive(Clone)] -pub struct Buffer> { - pub iter: I, - /// Span of the previous token. - pub prev_span: Span, - pub cur: TokenAndSpan, - /// Peeked token - pub next: Option, -} - -impl> Buffer { - fn bump(&mut self) -> Token { - let next = if let Some(next) = self.next.take() { - next - } else if let Some(next) = self.iter.next() { - next - } else { - TokenAndSpan::new(Token::Eof, self.prev_span, true) - }; - let prev = mem::replace(&mut self.cur, next); - self.prev_span = prev.span(); - prev.token - } -} - -impl<'a, I: Tokens> crate::common::parser::buffer::Buffer<'a> for Buffer { - type I = I; - type Next = TokenAndSpan; - type Token = Token; - type TokenAndSpan = TokenAndSpan; - - fn new(lexer: I) -> Self { - let start_pos = lexer.start_pos(); - let prev_span = Span::new_with_checked(start_pos, start_pos); - Buffer { - iter: lexer, - cur: TokenAndSpan::new(Token::Eof, prev_span, false), - prev_span, - next: None, - } - } - - #[inline(always)] - fn set_cur(&mut self, token: TokenAndSpan) { - self.cur = token; - } - - #[inline(always)] - fn next(&self) -> Option<&TokenAndSpan> { - self.next.as_ref() - } - - #[inline(always)] - fn set_next(&mut self, token: Option) { - self.next = token; - } - - #[inline(always)] - fn next_mut(&mut self) -> &mut Option { - &mut self.next - } - - #[inline] - fn cur(&self) -> &Token { - &self.cur.token - } - - fn peek<'b>(&'b mut self) -> Option<&'b Token> - where - TokenAndSpan: 'b, - { - debug_assert!( - self.cur() != &Token::Eof, - "parser should not call peek() without knowing current token" - ); - - if self.next().is_none() { - let next = self.iter.next(); - self.set_next(next); - } - - self.next().map(|ts| &ts.token) - } - - #[inline(always)] - fn get_cur(&self) -> &TokenAndSpan { - &self.cur - } - - #[inline(always)] - fn prev_span(&self) -> Span { - self.prev_span - } - - #[inline(always)] - fn iter(&self) -> &I { - &self.iter - } - - #[inline(always)] - fn iter_mut(&mut self) -> &mut I { - &mut self.iter - } - - fn bump(&mut self) { - let _ = Buffer::bump(self); - } - - fn expect_word_token_and_bump(&mut self) -> swc_atoms::Atom { - let t = self.bump(); - t.take_word(self).unwrap() - } - - fn expect_jsx_name_token_and_bump(&mut self) -> swc_atoms::Atom { - let t = self.bump(); - t.take_jsx_name(self) - } - - fn expect_jsx_text_token_and_bump(&mut self) -> (swc_atoms::Atom, swc_atoms::Atom) { - let t = self.bump(); - t.take_jsx_text(self) - } - - fn expect_number_token_and_bump(&mut self) -> (f64, swc_atoms::Atom) { - let t = self.bump(); - t.take_num(self) - } - - fn expect_string_token_and_bump(&mut self) -> (swc_atoms::Wtf8Atom, swc_atoms::Atom) { - let t = self.bump(); - t.take_str(self) - } - - fn expect_bigint_token_and_bump(&mut self) -> (Box, swc_atoms::Atom) { - let t = self.bump(); - t.take_bigint(self) - } - - fn expect_regex_token_and_bump(&mut self) -> (swc_atoms::Atom, swc_atoms::Atom) { - let t = self.bump(); - t.take_regexp(self) - } - - fn expect_template_token_and_bump( - &mut self, - ) -> ( - crate::common::lexer::LexResult, - swc_atoms::Atom, - ) { - let t = self.bump(); - t.take_template(self) - } - - fn expect_error_token_and_bump(&mut self) -> crate::error::Error { - let t = self.bump(); - t.take_error(self) - } - - fn expect_shebang_token_and_bump(&mut self) -> swc_atoms::Atom { - let t = self.bump(); - t.take_shebang(self) - } - - #[cold] - #[inline(never)] - fn dump_cur(&self) -> String { - let cur = self.cur(); - format!("{cur:?}") - } -} diff --git a/crates/swc_ecma_lexer/src/lexer/comments_buffer.rs b/crates/swc_ecma_lexer/src/lexer/comments_buffer.rs deleted file mode 100644 index b5dad4729b55..000000000000 --- a/crates/swc_ecma_lexer/src/lexer/comments_buffer.rs +++ /dev/null @@ -1,93 +0,0 @@ -use std::{iter::Rev, rc::Rc, vec::IntoIter}; - -use swc_common::{comments::Comment, BytePos}; - -use crate::common::lexer::comments_buffer::{ - BufferedComment, BufferedCommentKind, CommentsBufferTrait, -}; - -#[derive(Clone)] -pub struct CommentsBuffer { - comments: OneDirectionalList, - pending_leading: OneDirectionalList, -} - -impl Default for CommentsBuffer { - fn default() -> Self { - Self::new() - } -} - -impl CommentsBuffer { - pub fn new() -> Self { - Self { - comments: OneDirectionalList::new(), - pending_leading: OneDirectionalList::new(), - } - } -} - -impl CommentsBufferTrait for CommentsBuffer { - #[inline(always)] - fn push_comment(&mut self, comment: BufferedComment) { - self.comments.push(comment); - } - - #[inline(always)] - fn push_pending(&mut self, comment: Comment) { - self.pending_leading.push(comment); - } - - #[inline(always)] - fn take_comments(&mut self) -> impl Iterator + '_ { - self.comments.take_all() - } - - #[inline(always)] - fn pending_to_comment(&mut self, kind: BufferedCommentKind, pos: BytePos) { - for comment in self.pending_leading.take_all() { - let comment = BufferedComment { kind, pos, comment }; - self.comments.push(comment); - } - } -} - -/// A one direction linked list that can be cheaply -/// cloned with the clone maintaining its position in the list. -#[derive(Clone)] -struct OneDirectionalList { - last_node: Option>>, -} - -impl OneDirectionalList { - pub fn new() -> Self { - Self { last_node: None } - } - - pub fn take_all(&mut self) -> Rev> { - // these are stored in reverse, so we need to reverse them back - let mut items = Vec::new(); - let mut current_node = self.last_node.take(); - while let Some(node) = current_node { - let mut node = match Rc::try_unwrap(node) { - Ok(n) => n, - Err(n) => n.as_ref().clone(), - }; - items.push(node.item); - current_node = node.previous.take(); - } - items.into_iter().rev() - } - - pub fn push(&mut self, item: T) { - let previous = self.last_node.take(); - let new_item = OneDirectionalListNode { item, previous }; - self.last_node = Some(Rc::new(new_item)); - } -} - -#[derive(Clone)] -struct OneDirectionalListNode { - item: T, - previous: Option>>, -} diff --git a/crates/swc_ecma_lexer/src/lexer/jsx.rs b/crates/swc_ecma_lexer/src/lexer/jsx.rs deleted file mode 100644 index ad5e2c009932..000000000000 --- a/crates/swc_ecma_lexer/src/lexer/jsx.rs +++ /dev/null @@ -1,124 +0,0 @@ -use either::Either; - -use super::*; - -impl Lexer<'_> { - pub(super) fn read_jsx_token(&mut self) -> LexResult { - debug_assert!(self.syntax.jsx()); - - let start = self.input.cur_pos(); - let mut chunk_start = self.input.cur_pos(); - let mut value = String::new(); - - loop { - let cur = match self.input.cur() { - Some(c) => c as char, - None => { - let start = self.state.start; - self.error(start, SyntaxError::UnterminatedJSXContents)? - } - }; - let cur_pos = self.input.cur_pos(); - - match cur { - '<' if self.had_line_break_before_last() && self.is_str("<<<<<< ") => { - let span = Span::new_with_checked(cur_pos, cur_pos + BytePos(7)); - - self.emit_error_span(span, SyntaxError::TS1185); - self.skip_line_comment(6); - self.skip_space::(); - return self.read_token(); - } - '<' | '{' => { - // - if cur_pos == self.state.start { - if cur == '<' && self.state.is_expr_allowed { - unsafe { - // Safety: cur() was Some('<') - self.input.bump_bytes(1); - } - return Ok(Token::JSXTagStart); - } - return self.read_token(); - } - - let s = unsafe { - // Safety: We already checked for the range - self.input.slice(chunk_start, cur_pos) - }; - let value = if value.is_empty() { - // Fast path: We don't need to allocate extra buffer for value - self.atoms.atom(s) - } else { - value.push_str(s); - self.atoms.atom(value) - }; - - let raw = { - let s = unsafe { - // Safety: We already checked for the range - self.input.slice(start, cur_pos) - }; - self.atoms.atom(s) - }; - - return Ok(Token::JSXText { raw, value }); - } - '>' => { - self.emit_error( - cur_pos, - SyntaxError::UnexpectedTokenWithSuggestions { - candidate_list: vec!["`{'>'}`", "`>`"], - }, - ); - unsafe { - // Safety: cur() was Some('>') - self.input.bump_bytes(1) - } - } - '}' => { - self.emit_error( - cur_pos, - SyntaxError::UnexpectedTokenWithSuggestions { - candidate_list: vec!["`{'}'}`", "`}`"], - }, - ); - unsafe { - // Safety: cur() was Some('}') - self.input.bump_bytes(1) - } - } - '&' => { - value.push_str(unsafe { - // Safety: We already checked for the range - self.input.slice(chunk_start, cur_pos) - }); - - let jsx_entity = self.read_jsx_entity()?; - - value.push(jsx_entity.0); - chunk_start = self.input.cur_pos(); - } - - _ => { - if cur.is_line_terminator() { - value.push_str(unsafe { - // Safety: We already checked for the range - self.input.slice(chunk_start, cur_pos) - }); - match self.read_jsx_new_line(true)? { - Either::Left(s) => value.push_str(s), - Either::Right(c) => value.push(c), - } - chunk_start = self.input.cur_pos(); - } else { - unsafe { - // Safety: cur() was Some(c) - self.input.bump_bytes(1) - } - } - } - } - } - } -} diff --git a/crates/swc_ecma_lexer/src/lexer/mod.rs b/crates/swc_ecma_lexer/src/lexer/mod.rs deleted file mode 100644 index 6ca772843608..000000000000 --- a/crates/swc_ecma_lexer/src/lexer/mod.rs +++ /dev/null @@ -1,350 +0,0 @@ -//! ECMAScript lexer. - -use std::{cell::RefCell, char, iter::FusedIterator, rc::Rc}; - -use swc_atoms::{wtf8::Wtf8, AtomStoreCell}; -use swc_common::{ - comments::Comments, - input::{Input, StringInput}, - BytePos, Span, -}; -use swc_ecma_ast::{AssignOp, EsVersion}; - -pub use self::state::{TokenContext, TokenContexts, TokenFlags, TokenType}; -use self::table::{ByteHandler, BYTE_HANDLERS}; -use crate::{ - common::{ - lexer::{char::CharExt, fixed_len_span, pos_span, LexResult, Lexer as LexerTrait}, - syntax::{Syntax, SyntaxFlags}, - }, - error::{Error, SyntaxError}, - lexer::comments_buffer::CommentsBuffer, - tok, - token::{BinOpToken, Token, TokenAndSpan}, - Context, -}; - -mod comments_buffer; -mod jsx; -mod number; -mod state; -mod table; -#[cfg(test)] -mod tests; - -#[derive(Clone)] -pub struct Lexer<'a> { - comments: Option<&'a dyn Comments>, - /// [Some] if comment comment parsing is enabled. Otherwise [None] - comments_buffer: Option, - - pub ctx: Context, - input: StringInput<'a>, - start_pos: BytePos, - - state: self::state::State, - pub(crate) syntax: SyntaxFlags, - pub(crate) target: EsVersion, - - errors: Rc>>, - module_errors: Rc>>, - - atoms: Rc, -} - -impl FusedIterator for Lexer<'_> {} - -impl<'a> crate::common::lexer::Lexer<'a, TokenAndSpan> for Lexer<'a> { - type CommentsBuffer = CommentsBuffer; - type State = self::state::State; - type Token = self::Token; - - #[inline(always)] - fn input(&self) -> &StringInput<'a> { - &self.input - } - - #[inline(always)] - fn input_mut(&mut self) -> &mut StringInput<'a> { - &mut self.input - } - - #[inline(always)] - fn push_error(&mut self, error: crate::error::Error) { - self.errors.borrow_mut().push(error); - } - - #[inline(always)] - fn state(&self) -> &Self::State { - &self.state - } - - #[inline(always)] - fn state_mut(&mut self) -> &mut Self::State { - &mut self.state - } - - #[inline(always)] - fn comments(&self) -> Option<&'a dyn swc_common::comments::Comments> { - self.comments - } - - #[inline(always)] - fn comments_buffer(&self) -> Option<&Self::CommentsBuffer> { - self.comments_buffer.as_ref() - } - - #[inline(always)] - fn comments_buffer_mut(&mut self) -> Option<&mut Self::CommentsBuffer> { - self.comments_buffer.as_mut() - } - - #[inline(always)] - unsafe fn input_slice(&mut self, start: BytePos, end: BytePos) -> &'a str { - self.input.slice(start, end) - } - - #[inline(always)] - fn input_uncons_while(&mut self, f: impl FnMut(char) -> bool) -> &'a str { - self.input_mut().uncons_while(f) - } - - #[inline(always)] - fn atom<'b>(&self, s: impl Into>) -> swc_atoms::Atom { - self.atoms.atom(s) - } - - #[inline(always)] - fn wtf8_atom<'b>(&self, s: impl Into>) -> swc_atoms::Wtf8Atom { - self.atoms.wtf8_atom(s) - } -} - -impl<'a> Lexer<'a> { - pub fn new( - syntax: Syntax, - target: EsVersion, - input: StringInput<'a>, - comments: Option<&'a dyn Comments>, - ) -> Self { - let start_pos = input.last_pos(); - let syntax_flags = syntax.into_flags(); - - Lexer { - comments, - comments_buffer: comments.is_some().then(CommentsBuffer::new), - ctx: Default::default(), - input, - start_pos, - state: self::state::State::new(syntax_flags, start_pos), - syntax: syntax_flags, - target, - errors: Default::default(), - module_errors: Default::default(), - atoms: Default::default(), - } - } - - /// babel: `getTokenFromCode` - fn read_token(&mut self) -> LexResult { - let byte = match self.input.as_str().as_bytes().first() { - Some(&v) => v, - None => return Ok(Token::Eof), - }; - - let handler = unsafe { *(&BYTE_HANDLERS as *const ByteHandler).offset(byte as isize) }; - - match handler { - Some(handler) => handler(self), - None => { - let start = self.cur_pos(); - unsafe { - self.input.bump_bytes(1); - } - self.error_span( - pos_span(start), - SyntaxError::UnexpectedChar { c: byte as _ }, - ) - } - } - } - - fn read_token_plus_minus(&mut self) -> LexResult { - let start = self.cur_pos(); - - unsafe { - // Safety: cur() is Some(c), if this method is called. - self.input.bump_bytes(1); - } - - // '++', '--' - Ok(if self.input.cur() == Some(C) { - unsafe { - // Safety: cur() is Some(c) - self.input.bump_bytes(1); - } - - // Handle --> - if self.state.had_line_break && C == b'-' && self.eat(b'>') { - self.emit_module_mode_error(start, SyntaxError::LegacyCommentInModule); - self.skip_line_comment(0); - self.skip_space::(); - return self.read_token(); - } - - if C == b'+' { - Token::PlusPlus - } else { - Token::MinusMinus - } - // Safety: b'=' is ASCII. - } else if unsafe { self.input.eat_byte(b'=') } { - Token::AssignOp(if C == b'+' { - AssignOp::AddAssign - } else { - AssignOp::SubAssign - }) - } else { - Token::BinOp(if C == b'+' { - BinOpToken::Add - } else { - BinOpToken::Sub - }) - }) - } - - fn read_token_bang_or_eq(&mut self) -> LexResult { - let start = self.cur_pos(); - let had_line_break_before_last = self.had_line_break_before_last(); - - unsafe { - // Safety: cur() is Some(c) if this method is called. - self.input.bump_bytes(1); - } - - // Safety: b'=' is ASCII. - Ok(if unsafe { self.input.eat_byte(b'=') } { - // "==" - - // Safety: b'=' is ASCII. - if unsafe { self.input.eat_byte(b'=') } { - if C == b'!' { - Token::BinOp(BinOpToken::NotEqEq) - } else { - // ======= - // ^ - if had_line_break_before_last && self.is_str("====") { - self.emit_error_span(fixed_len_span(start, 7), SyntaxError::TS1185); - self.skip_line_comment(4); - self.skip_space::(); - return self.read_token(); - } - - Token::BinOp(BinOpToken::EqEqEq) - } - } else if C == b'!' { - Token::BinOp(BinOpToken::NotEq) - } else { - Token::BinOp(BinOpToken::EqEq) - } - // Safety: b'>' is ASCII. - } else if C == b'=' && unsafe { self.input.eat_byte(b'>') } { - // "=>" - - Token::Arrow - } else if C == b'!' { - Token::Bang - } else { - Token::AssignOp(AssignOp::Assign) - }) - } -} - -impl Lexer<'_> { - #[inline(never)] - fn read_token_lt_gt(&mut self) -> LexResult { - let had_line_break_before_last = self.had_line_break_before_last(); - let start = self.cur_pos(); - self.bump(); - - if self.syntax.typescript() - && self.ctx.contains(Context::InType) - && !self.ctx.contains(Context::ShouldNotLexLtOrGtAsType) - { - if C == b'<' { - return Ok(tok!('<')); - } else if C == b'>' { - return Ok(tok!('>')); - } - } - - // XML style comment. `"), - vec![Error::new(sp(0..3), SyntaxError::LegacyCommentInModule)] - ) -} - -#[test] -fn test262_lexer_error_0001() { - assert_eq!( - lex(Syntax::default(), "123..a(1)"), - vec![ - TokenAndSpan { - token: Num { - value: 123.0, - raw: atom!("123."), - }, - had_line_break: true, - span: Span { - lo: BytePos(1), - hi: BytePos(5), - } - }, - Dot.span(4..5), - "a".span(5..6), - LParen.span(6..7), - 1.span(7..8), - RParen.span(8..9), - ], - ) -} - -#[test] -fn test262_lexer_error_0002() { - assert_eq!( - lex(Syntax::default(), r"'use\x20strict';"), - vec![ - Token::Str { - value: atom!("use strict").into(), - raw: atom!("'use\\x20strict'"), - } - .span(0..15) - .lb(), - Semi.span(15..16), - ] - ); -} - -#[test] -fn test262_lexer_error_0003() { - assert_eq!(lex(Syntax::default(), r"\u0061"), vec!["a".span(0..6).lb()]); -} - -#[test] -fn test262_lexer_error_0004() { - assert_eq!( - lex_tokens(Syntax::default(), "+{} / 1"), - vec![tok!('+'), tok!('{'), tok!('}'), tok!('/'), 1.into_token()] - ); -} - -#[test] -fn ident_escape_unicode() { - assert_eq!( - lex(Syntax::default(), r"a\u0061"), - vec!["aa".span(0..7).lb()] - ); -} - -#[test] -fn ident_escape_unicode_2() { - assert_eq!(lex(Syntax::default(), "℘℘"), vec!["℘℘".span(0..6).lb()]); - - assert_eq!( - lex(Syntax::default(), r"℘\u2118"), - vec!["℘℘".span(0..9).lb()] - ); -} - -#[test] -fn tpl_multiline() { - assert_eq!( - lex_tokens( - Syntax::default(), - "`this -is -multiline`" - ), - vec![ - tok!('`'), - Token::Template { - cooked: Ok(atom!("this\nis\nmultiline").into()), - raw: atom!("this\nis\nmultiline"), - }, - tok!('`'), - ] - ); -} - -#[test] -fn tpl_raw_unicode_escape() { - assert_eq!( - lex_tokens(Syntax::default(), r"`\u{0010}`"), - vec![ - tok!('`'), - Token::Template { - cooked: Ok(format!("{}", '\u{0010}').into()), - raw: atom!("\\u{0010}"), - }, - tok!('`'), - ] - ); -} - -#[test] -fn tpl_invalid_unicode_escape() { - assert_eq!( - lex_tokens(Syntax::default(), r"`\unicode`"), - vec![ - tok!('`'), - Token::Template { - cooked: Err(Error::new( - Span { - lo: BytePos(2), - hi: BytePos(4), - }, - SyntaxError::BadCharacterEscapeSequence { - expected: "4 hex characters" - } - )), - raw: atom!("\\unicode"), - }, - tok!('`'), - ] - ); - assert_eq!( - lex_tokens(Syntax::default(), r"`\u{`"), - vec![ - tok!('`'), - Token::Template { - cooked: Err(Error::new( - Span { - lo: BytePos(2), - hi: BytePos(5), - }, - SyntaxError::BadCharacterEscapeSequence { - expected: "1-6 hex characters" - } - )), - raw: atom!("\\u{"), - }, - tok!('`'), - ] - ); - assert_eq!( - lex_tokens(Syntax::default(), r"`\xhex`"), - vec![ - tok!('`'), - Token::Template { - cooked: Err(Error::new( - Span { - lo: BytePos(2), - hi: BytePos(4), - }, - SyntaxError::BadCharacterEscapeSequence { - expected: "2 hex characters" - } - )), - raw: atom!("\\xhex"), - }, - tok!('`'), - ] - ); -} - -#[test] -fn str_escape() { - assert_eq!( - lex_tokens(Syntax::default(), r"'\n'"), - vec![Token::Str { - value: atom!("\n").into(), - raw: atom!("'\\n'"), - }] - ); -} - -#[test] -fn str_escape_2() { - assert_eq!( - lex_tokens(Syntax::default(), r"'\\n'"), - vec![Token::Str { - value: atom!("\\n").into(), - raw: atom!("'\\\\n'"), - }] - ); -} - -#[test] -fn str_escape_3() { - assert_eq!( - lex_tokens(Syntax::default(), r"'\x00'"), - vec![Token::Str { - value: atom!("\x00").into(), - raw: atom!("'\\x00'"), - }] - ); -} - -#[test] -fn str_escape_hex() { - assert_eq!( - lex(Syntax::default(), r"'\x61'"), - vec![Token::Str { - value: atom!("a").into(), - raw: atom!("'\\x61'"), - } - .span(0..6) - .lb(),] - ); -} - -#[test] -fn str_escape_octal() { - assert_eq!( - lex(Syntax::default(), r"'Hello\012World'"), - vec![Token::Str { - value: atom!("Hello\nWorld").into(), - raw: atom!("'Hello\\012World'"), - } - .span(0..16) - .lb(),] - ) -} - -#[test] -fn str_escape_unicode_long() { - assert_eq!( - lex(Syntax::default(), r"'\u{00000000034}'"), - vec![Token::Str { - value: atom!("4").into(), - raw: atom!("'\\u{00000000034}'"), - } - .span(0..17) - .lb(),] - ); -} - -#[test] -fn regexp_unary_void() { - assert_eq!( - lex(Syntax::default(), "void /test/"), - vec![ - Void.span(0..4).lb(), - BinOp(Div).span(5), - Word(Word::Ident("test".into())).span(6..10), - BinOp(Div).span(10), - ] - ); - assert_eq!( - lex(Syntax::default(), "void (/test/)"), - vec![ - Void.span(0..4).lb(), - LParen.span(5..6), - BinOp(Div).span(6), - Word(Word::Ident("test".into())).span(7..11), - BinOp(Div).span(11), - RParen.span(12..13), - ] - ); -} - -#[test] -fn non_regexp_unary_plus() { - assert_eq!( - lex(Syntax::default(), "+{} / 1"), - vec![ - tok!('+').span(0..1).lb(), - tok!('{').span(1..2), - tok!('}').span(2..3), - tok!('/').span(4..5), - 1.span(6..7), - ] - ); -} - -// ---------- - -#[test] -fn paren_semi() { - assert_eq!( - lex(Syntax::default(), "();"), - vec![LParen.span(0).lb(), RParen.span(1), Semi.span(2)] - ); -} - -#[test] -fn ident_paren() { - assert_eq!( - lex(Syntax::default(), "a(bc);"), - vec![ - "a".span(0).lb(), - LParen.span(1), - "bc".span(2..4), - RParen.span(4), - Semi.span(5), - ] - ); -} - -#[test] -fn read_word() { - assert_eq!( - lex(Syntax::default(), "a b c"), - vec!["a".span(0).lb(), "b".span(2), "c".span(4)] - ) -} - -#[test] -fn simple_regex() { - assert_eq!( - lex(Syntax::default(), "x = /42/i"), - vec![ - "x".span(0).lb(), - Assign.span(2), - BinOp(Div).span(4), - 42.span(5..7), - BinOp(Div).span(7), - Word(Word::Ident("i".into())).span(8), - ], - ); - - assert_eq!( - lex(Syntax::default(), "/42/"), - vec![ - TokenAndSpan { - token: Token::BinOp(BinOpToken::Div), - had_line_break: true, - span: Span { - lo: BytePos(1), - hi: BytePos(2), - }, - }, - 42.span(1..3), - BinOp(Div).span(3) - ] - ); -} - -#[test] -fn complex_regex() { - assert_eq!( - lex_tokens(Syntax::default(), "f(); function foo() {} /42/i"), - vec![ - Word(Word::Ident("f".into())), - LParen, - RParen, - Semi, - Word(Word::Keyword(Function)), - Word(Word::Ident("foo".into())), - LParen, - RParen, - LBrace, - RBrace, - BinOp(Div), - Num { - value: 42.0, - raw: Atom::new("42") - }, - BinOp(Div), - Word(Word::Ident("i".into())), - ] - ) -} - -#[test] -fn simple_div() { - assert_eq!( - lex(Syntax::default(), "a / b"), - vec!["a".span(0).lb(), Div.span(2), "b".span(4)], - ); -} - -#[test] -fn complex_divide() { - assert_eq!( - lex_tokens(Syntax::default(), "x = function foo() {} /a/i"), - vec![ - Word(Word::Ident("x".into())), - AssignOp(Assign), - Word(Word::Keyword(Function)), - Word(Word::Ident("foo".into())), - LParen, - RParen, - LBrace, - RBrace, - BinOp(Div), - Word(Word::Ident("a".into())), - BinOp(Div), - Word(Word::Ident("i".into())), - ], - "/ should be parsed as div operator" - ) -} - -// ---------- Tests from tc39 spec - -#[test] -fn spec_001() { - let expected = vec![ - Word(Word::Ident("a".into())), - AssignOp(Assign), - Word(Word::Ident("b".into())), - BinOp(Div), - Word(Word::Ident("hi".into())), - BinOp(Div), - Word(Word::Ident("g".into())), - Dot, - Word(Word::Ident("exec".into())), - LParen, - Word(Word::Ident("c".into())), - RParen, - Dot, - Word(Word::Ident("map".into())), - LParen, - Word(Word::Ident("d".into())), - RParen, - Semi, - ]; - - assert_eq!( - lex_tokens( - Syntax::default(), - "a = b -/hi/g.exec(c).map(d);" - ), - expected - ); - assert_eq!( - lex_tokens(Syntax::default(), "a = b / hi / g.exec(c).map(d);"), - expected - ); -} - -// ---------- Tests ported from esprima - -#[test] -fn after_if() { - assert_eq!( - lex(Syntax::default(), "if(x){} /y/.test(z)"), - vec![ - Keyword::If.span(0..2).lb(), - LParen.span(2), - "x".span(3), - RParen.span(4), - LBrace.span(5), - RBrace.span(6), - Div.span(8), - "y".span(9), - Div.span(10), - Dot.span(11), - "test".span(12..16), - LParen.span(16), - "z".span(17), - RParen.span(18), - ], - ) -} - -// #[test] -// #[ignore] -// fn leading_comment() { -// assert_eq!( -// vec![ -// BlockComment(" hello world ".into()).span(0..17), -// Regex("42".into(), "".into()).span(17..21), -// ], -// lex(Syntax::default(), "/* hello world */ /42/") -// ) -// } - -// #[test] -// #[ignore] -// fn line_comment() { -// assert_eq!( -// vec![ -// Keyword::Var.span(0..3), -// "answer".span(4..10), -// Assign.span(11), -// 42.span(13..15), -// LineComment(" the Ultimate".into()).span(17..32), -// ], -// lex(Syntax::default(), "var answer = 42 // the Ultimate"), -// ) -// } - -#[test] -fn migrated_0002() { - assert_eq!( - lex(Syntax::default(), "tokenize(/42/)"), - vec![ - "tokenize".span(0..8).lb(), - LParen.span(8), - BinOp(Div).span(9), - 42.span(10..12), - BinOp(Div).span(12), - RParen.span(13), - ], - ) -} - -#[test] -fn migrated_0003() { - assert_eq!( - lex(Syntax::default(), "(false) /42/"), - vec![ - LParen.span(0).lb(), - Word::False.span(1..6), - RParen.span(6), - Div.span(8), - 42.span(9..11), - Div.span(11), - ], - ) -} - -#[test] -fn migrated_0004() { - assert_eq!( - lex(Syntax::default(), "function f(){} /42/"), - vec![ - Function.span(0..8).lb(), - "f".span(9), - LParen.span(10), - RParen.span(11), - LBrace.span(12), - RBrace.span(13), - BinOp(Div).span(15), - 42.span(16..18), - BinOp(Div).span(18), - ] - ); -} - -// This test seems wrong. -// -// #[test] -// fn migrated_0005() { -// assert_eq!( -// vec![ -// Function.span(0..8), -// LParen.span(9), -// RParen.span(10), -// LBrace.span(11), -// RBrace.span(12), -// Div.span(13), -// 42.span(14..16), -// ], -// lex(Syntax::default(), "function (){} /42") -// ); -// } - -#[test] -fn migrated_0006() { - // This test seems wrong. - // assert_eq!( - // vec![LBrace.span(0).lb(), RBrace.span(1), Div.span(3), 42.span(4..6)], - // lex(Syntax::default(), "{} /42") - // ) - - assert_eq!( - lex(Syntax::default(), "{} /42/"), - vec![ - LBrace.span(0).lb(), - RBrace.span(1), - BinOp(Div).span(3), - TokenAndSpan { - token: Num { - value: 42.0, - raw: atom!("42"), - }, - had_line_break: false, - span: Span { - lo: BytePos(5), - hi: BytePos(7), - } - }, - BinOp(Div).span(6), - ], - ) -} - -#[test] -fn str_lit() { - assert_eq!( - lex_tokens(Syntax::default(), "'abcde'"), - vec![Token::Str { - value: atom!("abcde").into(), - raw: atom!("'abcde'"), - }], - ); - assert_eq!( - lex_tokens(Syntax::default(), "\"abcde\""), - vec![Token::Str { - value: atom!("abcde").into(), - raw: atom!("\"abcde\""), - }], - ); - assert_eq!( - lex_tokens(Syntax::default(), "'русский'"), - vec![Token::Str { - value: atom!("русский").into(), - raw: atom!("'русский'"), - }], - ); - assert_eq!( - lex_tokens(Syntax::default(), "'\\x32'"), - vec![Token::Str { - value: atom!("2").into(), - raw: atom!("'\\x32'"), - }], - ); - assert_eq!( - lex_tokens(Syntax::default(), "'\\u1111'"), - vec![Token::Str { - value: atom!("ᄑ").into(), - raw: atom!("'\\u1111'"), - }], - ); - assert_eq!( - lex_tokens(Syntax::default(), "'\\u{1111}'"), - vec![Token::Str { - value: atom!("ᄑ").into(), - raw: atom!("'\\u{1111}'"), - }], - ); - assert_eq!( - lex_tokens(Syntax::default(), "'\t'"), - vec![Token::Str { - value: atom!("\t").into(), - raw: atom!("'\t'"), - }], - ); - assert_eq!( - lex_tokens(Syntax::default(), "'\\n'"), - vec![Token::Str { - value: atom!("\n").into(), - raw: atom!("'\\n'"), - }], - ); - assert_eq!( - lex_tokens(Syntax::default(), "'\\\nabc'"), - vec![Token::Str { - value: atom!("abc").into(), - raw: atom!("'\\\nabc'"), - }] - ); - assert_eq!( - lex_tokens(Syntax::default(), "''"), - vec![Token::Str { - value: atom!("").into(), - raw: atom!("''"), - }], - ); - assert_eq!( - lex_tokens(Syntax::default(), "'\\''"), - vec![Token::Str { - value: atom!("'").into(), - raw: atom!("'\\''"), - }], - ); - assert_eq!( - lex_tokens(Syntax::default(), "\"\""), - vec![Token::Str { - value: atom!("").into(), - raw: atom!("\"\""), - }], - ); - assert_eq!( - lex_tokens(Syntax::default(), "\"\\\"\""), - vec![Token::Str { - value: atom!("\"").into(), - raw: atom!("\"\\\"\""), - }], - ); - assert_eq!( - lex_tokens(Syntax::default(), "'\\0'"), - vec![Token::Str { - value: atom!("\u{0000}").into(), - raw: atom!("'\\0'"), - }], - ); - assert_eq!( - lex_tokens(Syntax::default(), "'\\n'"), - vec![Token::Str { - value: atom!("\n").into(), - raw: atom!("'\\n'"), - }], - ); - assert_eq!( - lex_tokens(Syntax::default(), "'\\r'"), - vec![Token::Str { - value: atom!("\r").into(), - raw: atom!("'\\r'"), - }], - ); - assert_eq!( - lex_tokens(Syntax::default(), "'\\012'"), - vec![Token::Str { - value: atom!("\n").into(), - raw: atom!("'\\012'"), - }], - ); - assert_eq!( - lex_tokens(Syntax::default(), "'\\07'"), - vec![Token::Str { - value: atom!("\u{0007}").into(), - raw: atom!("'\\07'"), - }], - ); - assert_eq!( - lex_tokens(Syntax::default(), "'\\08'"), - vec![Token::Str { - value: atom!("\u{0000}8").into(), - raw: atom!("'\\08'"), - }], - ); -} - -#[test] -fn tpl_empty() { - assert_eq!( - lex_tokens(Syntax::default(), r#"``"#), - vec![ - tok!('`'), - Template { - raw: atom!(""), - cooked: Ok(atom!("").into()), - }, - tok!('`') - ] - ) -} - -#[test] -fn tpl() { - assert_eq!( - lex_tokens(Syntax::default(), r#"`${a}`"#), - vec![ - tok!('`'), - Template { - raw: atom!(""), - cooked: Ok(atom!("").into()), - }, - tok!("${"), - Word(Word::Ident("a".into())), - tok!('}'), - Template { - raw: atom!(""), - cooked: Ok(atom!("").into()), - }, - tok!('`'), - ] - ); - - assert_eq!( - lex_tokens(Syntax::default(), r"`te\nst${a}test`"), - vec![ - tok!('`'), - Template { - raw: atom!("te\\nst"), - cooked: Ok(atom!("te\nst").into()), - }, - tok!("${"), - Word(Word::Ident("a".into())), - tok!('}'), - Template { - raw: atom!("test"), - cooked: Ok(atom!("test").into()), - }, - tok!('`'), - ] - ); - - let mut buf = Wtf8Buf::new(); - buf.push(unsafe { CodePoint::from_u32_unchecked(0xd800) }); - assert_eq!( - lex_tokens(Syntax::default(), r"`\uD800`"), - vec![ - tok!('`'), - Template { - raw: atom!("\\uD800"), - cooked: Ok(buf.into()), - }, - tok!('`'), - ] - ); -} - -#[test] -fn comment() { - assert_eq!( - lex( - Syntax::default(), - "// foo -a" - ), - vec![TokenAndSpan { - token: Word(Word::Ident("a".into())), - span: sp(7..8), - // first line - had_line_break: true - }] - ); -} - -#[test] -fn comment_2() { - assert_eq!( - lex( - Syntax::default(), - "// foo -// bar -a" - ), - vec![TokenAndSpan { - token: Word(Word::Ident("a".into())), - span: sp(14..15), - // first line - had_line_break: true - }] - ); -} - -#[test] -fn jsx_01() { - assert_eq!( - lex_tokens( - crate::Syntax::Es(EsSyntax { - jsx: true, - ..Default::default() - }), - "" - ), - vec![ - Token::JSXTagStart, - Token::JSXName { name: atom!("a") }, - tok!('/'), - Token::JSXTagEnd, - ] - ); -} - -#[test] -fn jsx_02() { - assert_eq!( - lex_tokens( - crate::Syntax::Es(EsSyntax { - jsx: true, - ..Default::default() - }), - "foo" - ), - vec![ - Token::JSXTagStart, - Token::JSXName { name: atom!("a") }, - Token::JSXTagEnd, - Token::JSXText { - raw: atom!("foo"), - value: atom!("foo") - }, - Token::JSXTagStart, - tok!('/'), - Token::JSXName { name: atom!("a") }, - Token::JSXTagEnd, - ] - ); -} - -#[test] -fn jsx_03() { - assert_eq!( - lex_tokens( - crate::Syntax::Es(EsSyntax { - jsx: true, - ..Default::default() - }), - "
" - ), - vec![ - Token::JSXTagStart, - Token::JSXName { name: atom!("a") }, - Token::JSXTagEnd, - // - Token::JSXTagStart, - Token::JSXName { name: atom!("br") }, - tok!('/'), - Token::JSXTagEnd, - // - Token::JSXTagStart, - tok!('/'), - Token::JSXName { name: atom!("a") }, - Token::JSXTagEnd, - ] - ); -} - -#[test] -fn jsx_04() { - assert_eq!( - lex_tokens( - crate::Syntax::Es(EsSyntax { - jsx: true, - ..Default::default() - }), - "yield " - ), - vec![ - Token::Word(Word::Keyword(Yield)), - Token::JSXTagStart, - Token::JSXName { name: atom!("a") }, - Token::JSXTagEnd, - // - Token::JSXTagStart, - tok!('/'), - Token::JSXName { name: atom!("a") }, - Token::JSXTagEnd, - ] - ); -} - -#[test] -fn max_integer() { - lex_tokens(crate::Syntax::default(), "1.7976931348623157e+308"); -} - -#[test] -fn shebang() { - assert_eq!( - lex_tokens(crate::Syntax::default(), "#!/usr/bin/env node",), - vec![Token::Shebang(atom!("/usr/bin/env node"))] - ); -} - -#[test] -fn empty() { - assert_eq!(lex_tokens(crate::Syntax::default(), "",), Vec::new()); -} - -#[test] -fn issue_191() { - assert_eq!( - lex_tokens( - crate::Syntax::Es(EsSyntax { - jsx: true, - ..Default::default() - }), - "`${foo}`" - ), - vec![ - tok!('`'), - Token::Template { - raw: atom!(""), - cooked: Ok(atom!("").into()), - }, - tok!("${"), - Token::Word(Word::Ident("foo".into())), - tok!('}'), - Token::Template { - raw: atom!(""), - cooked: Ok(atom!("").into()), - }, - tok!('`') - ] - ); -} - -#[test] -fn issue_5722() { - assert_eq!( - lex_tokens(Default::default(), "`${{class: 0}}`"), - vec![ - tok!('`'), - Template { - raw: atom!(""), - cooked: Ok(atom!("").into()), - }, - tok!("${"), - tok!('{'), - Word(Word::Keyword(Keyword::Class)), - tok!(':'), - Num { - value: 0.into(), - raw: atom!("0") - }, - tok!('}'), - tok!('}'), - Template { - raw: atom!(""), - cooked: Ok(atom!("").into()), - }, - tok!('`'), - ] - ) -} - -#[test] -fn jsx_05() { - assert_eq!( - lex_tokens( - crate::Syntax::Es(EsSyntax { - jsx: true, - ..Default::default() - }), - "{}" - ), - vec![ - Token::JSXTagStart, - Token::JSXName { name: atom!("a") }, - Token::JSXTagEnd, - // - tok!('{'), - tok!('}'), - // - Token::JSXTagStart, - tok!('/'), - Token::JSXName { name: atom!("a") }, - Token::JSXTagEnd, - ] - ); -} - -#[test] -fn issue_299_01() { - assert_eq!( - lex_tokens( - crate::Syntax::Es(EsSyntax { - jsx: true, - ..Default::default() - }), - "ABC;" - ), - vec![ - Token::JSXTagStart, - Token::JSXName { - name: atom!("Page") - }, - Token::JSXName { name: atom!("num") }, - tok!('='), - Token::Str { - value: atom!("\\ ").into(), - raw: atom!("'\\ '"), - }, - Token::JSXTagEnd, - JSXText { - raw: atom!("ABC"), - value: atom!("ABC") - }, - JSXTagStart, - tok!('/'), - JSXName { - name: atom!("Page") - }, - JSXTagEnd, - Semi, - ] - ); -} - -#[test] -fn issue_299_02() { - assert_eq!( - lex_tokens( - crate::Syntax::Es(EsSyntax { - jsx: true, - ..Default::default() - }), - "ABC;" - ), - vec![ - Token::JSXTagStart, - Token::JSXName { - name: atom!("Page") - }, - Token::JSXName { name: atom!("num") }, - tok!('='), - Token::Str { - value: atom!("\\\\").into(), - raw: atom!("'\\\\'"), - }, - Token::JSXTagEnd, - JSXText { - raw: atom!("ABC"), - value: atom!("ABC") - }, - JSXTagStart, - tok!('/'), - JSXName { - name: atom!("Page") - }, - JSXTagEnd, - Semi, - ] - ); -} - -#[test] -fn jsx_string_1() { - assert_eq!( - lex_tokens( - crate::Syntax::Es(EsSyntax { - jsx: true, - ..Default::default() - }), - "ABC;" - ), - vec![ - Token::JSXTagStart, - Token::JSXName { - name: atom!("Page") - }, - Token::JSXName { name: atom!("num") }, - tok!('='), - Token::Str { - value: atom!("abc").into(), - raw: atom!("'abc'"), - }, - Token::JSXTagEnd, - JSXText { - raw: atom!("ABC"), - value: atom!("ABC") - }, - JSXTagStart, - tok!('/'), - JSXName { - name: atom!("Page") - }, - JSXTagEnd, - Semi, - ] - ); -} - -#[test] -fn jsx_string_2() { - assert_eq!( - lex_tokens( - crate::Syntax::Es(EsSyntax { - jsx: true, - ..Default::default() - }), - "ABC;" - ), - vec![ - Token::JSXTagStart, - Token::JSXName { - name: atom!("Page") - }, - Token::JSXName { name: atom!("num") }, - tok!('='), - Token::Str { - value: atom!("abc").into(), - raw: atom!("\"abc\""), - }, - Token::JSXTagEnd, - JSXText { - raw: atom!("ABC"), - value: atom!("ABC") - }, - JSXTagStart, - tok!('/'), - JSXName { - name: atom!("Page") - }, - JSXTagEnd, - Semi, - ] - ); -} - -#[test] -fn jsx_string_3() { - assert_eq!( - lex_tokens( - crate::Syntax::Es(EsSyntax { - jsx: true, - ..Default::default() - }), - "ABC;" - ), - vec![ - Token::JSXTagStart, - Token::JSXName { - name: atom!("Page") - }, - Token::JSXName { name: atom!("num") }, - tok!('='), - Token::Str { - value: atom!("\n").into(), - raw: atom!("'\n'"), - }, - Token::JSXTagEnd, - JSXText { - raw: atom!("ABC"), - value: atom!("ABC") - }, - JSXTagStart, - tok!('/'), - JSXName { - name: atom!("Page") - }, - JSXTagEnd, - Semi, - ] - ); -} - -#[test] -fn jsx_string_4() { - assert_eq!( - lex_tokens( - crate::Syntax::Es(EsSyntax { - jsx: true, - ..Default::default() - }), - "ABC;" - ), - vec![ - Token::JSXTagStart, - Token::JSXName { - name: atom!("Page") - }, - Token::JSXName { name: atom!("num") }, - tok!('='), - Token::Str { - value: atom!("³").into(), - raw: atom!("'³'"), - }, - Token::JSXTagEnd, - JSXText { - raw: atom!("ABC"), - value: atom!("ABC") - }, - JSXTagStart, - tok!('/'), - JSXName { - name: atom!("Page") - }, - JSXTagEnd, - Semi, - ] - ); -} - -#[test] -fn jsx_string_5() { - assert_eq!( - lex_tokens( - crate::Syntax::Es(EsSyntax { - jsx: true, - ..Default::default() - }), - "ABC;" - ), - vec![ - Token::JSXTagStart, - Token::JSXName { - name: atom!("Page") - }, - Token::JSXName { name: atom!("num") }, - tok!('='), - Token::Str { - value: atom!("*").into(), - raw: atom!("'*'"), - }, - Token::JSXTagEnd, - JSXText { - raw: atom!("ABC"), - value: atom!("ABC") - }, - JSXTagStart, - tok!('/'), - JSXName { - name: atom!("Page") - }, - JSXTagEnd, - Semi, - ] - ); -} - -#[test] -fn jsx_string_6() { - assert_eq!( - lex_tokens( - crate::Syntax::Es(EsSyntax { - jsx: true, - ..Default::default() - }), - "ABC;" - ), - vec![ - Token::JSXTagStart, - Token::JSXName { - name: atom!("Page") - }, - Token::JSXName { name: atom!("num") }, - tok!('='), - Token::Str { - value: atom!("#").into(), - raw: atom!("'#'"), - }, - Token::JSXTagEnd, - JSXText { - raw: atom!("ABC"), - value: atom!("ABC") - }, - JSXTagStart, - tok!('/'), - JSXName { - name: atom!("Page") - }, - JSXTagEnd, - Semi, - ] - ); -} - -#[test] -fn jsx_string_7() { - assert_eq!( - lex_tokens( - crate::Syntax::Es(EsSyntax { - jsx: true, - ..Default::default() - }), - "ABC;" - ), - vec![ - Token::JSXTagStart, - Token::JSXName { - name: atom!("Page") - }, - Token::JSXName { name: atom!("num") }, - tok!('='), - Token::Str { - value: atom!("&").into(), - raw: atom!("'&'"), - }, - Token::JSXTagEnd, - JSXText { - raw: atom!("ABC"), - value: atom!("ABC") - }, - JSXTagStart, - tok!('/'), - JSXName { - name: atom!("Page") - }, - JSXTagEnd, - Semi, - ] - ); -} - -#[test] -fn jsx_string_8() { - assert_eq!( - lex_tokens( - crate::Syntax::Es(EsSyntax { - jsx: true, - ..Default::default() - }), - "ABC;" - ), - vec![ - Token::JSXTagStart, - Token::JSXName { - name: atom!("Page") - }, - Token::JSXName { name: atom!("num") }, - tok!('='), - Token::Str { - value: atom!("&;").into(), - raw: atom!("'&;'"), - }, - Token::JSXTagEnd, - JSXText { - raw: atom!("ABC"), - value: atom!("ABC") - }, - JSXTagStart, - tok!('/'), - JSXName { - name: atom!("Page") - }, - JSXTagEnd, - Semi, - ] - ); -} - -#[test] -fn jsx_string_9() { - assert_eq!( - lex_tokens( - crate::Syntax::Es(EsSyntax { - jsx: true, - ..Default::default() - }), - "ABC;" - ), - vec![ - Token::JSXTagStart, - Token::JSXName { - name: atom!("Page") - }, - Token::JSXName { name: atom!("num") }, - tok!('='), - Token::Str { - value: atom!("&&").into(), - raw: atom!("'&&'"), - }, - Token::JSXTagEnd, - JSXText { - raw: atom!("ABC"), - value: atom!("ABC") - }, - JSXTagStart, - tok!('/'), - JSXName { - name: atom!("Page") - }, - JSXTagEnd, - Semi, - ] - ); -} - -#[test] -fn issue_316() { - assert_eq!( - lex_tokens(Default::default(), "'Hi\\r\\n..'"), - vec![Token::Str { - value: atom!("Hi\r\n..").into(), - raw: atom!("'Hi\\r\\n..'"), - }] - ); -} - -#[test] -fn issue_401() { - assert_eq!( - lex_tokens(Default::default(), "'17' as const"), - vec![ - Token::Str { - value: atom!("17").into(), - raw: atom!("'17'"), - }, - tok!("as"), - tok!("const") - ], - ); -} - -#[test] -fn issue_481() { - assert_eq!( - lex_tokens( - crate::Syntax::Es(EsSyntax { - jsx: true, - ..Default::default() - }), - " {foo}" - ), - vec![ - Token::JSXTagStart, - Token::JSXName { - name: atom!("span") - }, - Token::JSXTagEnd, - JSXText { - raw: atom!(" "), - value: atom!(" ") - }, - LBrace, - Word(Word::Ident("foo".into())), - RBrace, - JSXTagStart, - BinOp(Div), - JSXName { - name: atom!("span") - }, - JSXTagEnd, - ] - ); -} - -#[test] -fn issue_915_1() { - assert_eq!( - lex_tokens(crate::Syntax::Es(Default::default()), r#"encode("\r\n")"#), - vec![ - Word(Word::Ident("encode".into())), - LParen, - Token::Str { - value: atom!("\r\n").into(), - raw: atom!("\"\\r\\n\""), - }, - RParen - ] - ); -} - -const COLOR_JS_CODE: &str = include_str!("../../../swc_ecma_parser/benches/files/colors.js"); - -#[bench] -fn lex_colors_js(b: &mut Bencher) { - b.bytes = COLOR_JS_CODE.len() as _; - - b.iter(|| { - let _ = with_lexer( - Syntax::default(), - Default::default(), - COLOR_JS_CODE, - |lexer| { - for t in lexer { - black_box(t); - } - Ok(()) - }, - ); - }); -} - -#[bench] -fn lex_colors_ts(b: &mut Bencher) { - b.bytes = COLOR_JS_CODE.len() as _; - - b.iter(|| { - let _ = with_lexer( - Syntax::Typescript(Default::default()), - Default::default(), - COLOR_JS_CODE, - |lexer| { - for t in lexer { - black_box(t); - } - Ok(()) - }, - ); - }); -} - -/// Benchmarks [Lexer] using [Iterator] interface. -fn bench_simple(b: &mut Bencher, s: &str) { - bench(b, Default::default(), s) -} - -/// Benchmarks [Lexer] using [Iterator] interface. -fn bench(b: &mut Bencher, syntax: Syntax, s: &str) { - b.bytes = s.len() as _; - - b.iter(|| { - let _ = with_lexer(syntax, Default::default(), s, |lexer| { - for t in lexer { - black_box(t); - } - Ok(()) - }); - }); -} - -#[bench] -fn lex_large_number(b: &mut Bencher) { - bench_simple( - b, - "10000000000000000; - 571293857289; - 32147859245; - 129478120974; - 238597230957293; - 542375984375; - 349578395; - 34857983412590716249; - 1238570129; - 123875102935;", - ); -} - -#[bench] -#[allow(clippy::octal_escapes)] -fn lex_escaped_char(b: &mut Bencher) { - bench_simple( - b, - "'\\x00\\01\\02\\03\\x00\\01\\02\\03\\x00\\01\\02\\03\\x00\\01\\02\\03\\x00\\01\\02\\03\\\ - x00\\01\\02\\03\\x00\\01\\02\\03\\x00\\01\\02\\03\\x00\\01\\02\\03\\x00\\01\\02\\03\\x00\\ - \ - \01\\02\\03\\x00\\01\\02\\03\\x00\\01\\02\\03\\x00\\01\\02\\03\\x00\\01\\02\\03\\x00\\01\\ - \02\\03'", - ); -} - -#[bench] -fn lex_legacy_octal_lit(b: &mut Bencher) { - bench_simple( - b, - "01756123617;01756123617;01756123617;01756123617;01756123617;01756123617;01756123617;\ - 01756123617;01756123617;01756123617;01756123617;01756123617;01756123617;01756123617;\ - 01756123617;01756123617;01756123617;01756123617;01756123617;01756123617;01756123617;\ - 01756123617;01756123617;01756123617;01756123617;01756123617;01756123617;01756123617;\ - 01756123617;01756123617;01756123617;01756123617;01756123617;01756123617;01756123617;\ - 01756123617;01756123617;01756123617;01756123617;01756123617;01756123617;01756123617;\ - 01756123617;01756123617;01756123617;01756123617;01756123617;01756123617;01756123617;\ - 01756123617;01756123617;", - ); -} - -#[bench] -fn lex_dec_lit(b: &mut Bencher) { - bench_simple( - b, - "14389675923;14389675923;14389675923;14389675923;14389675923;14389675923;14389675923;\ - 14389675923;14389675923;14389675923;14389675923;14389675923;14389675923;14389675923;\ - 14389675923;14389675923;14389675923;14389675923;14389675923;14389675923;14389675923;\ - 14389675923;14389675923;14389675923;14389675923;14389675923;14389675923;14389675923;\ - 14389675923;14389675923;14389675923;14389675923;14389675923;14389675923;14389675923;\ - 14389675923;14389675923;14389675923;14389675923;14389675923;14389675923;14389675923;\ - 14389675923;14389675923;14389675923;", - ); -} - -#[bench] -fn lex_ident(b: &mut Bencher) { - bench_simple( - b, - "Lorem;Ipsum;is;simply;dummy;text;of;the;printing;and;typesetting;industry;Lorem;Ipsum;\ - has;been;the;industry;s;standard;dummy;text;ever;since;the;1500s;when;an;unknown;printer;\ - took;a;galley;of;type;and;scrambled;it;to;make;a;type;specimen;book;It;has;survived;not;\ - only;five;centuries;but;also;the;leap;into;electronic;typesetting;remaining;essentially;\ - unchanged;It;was;popularised;in;the;1960s;with;the;release;of;Letraset;sheets;containing;\ - Lorem;Ipsum;passages;and;more;recently;with;desktop;publishing;software;like;Aldus;\ - PageMaker;including;versions;of;Lorem;Ipsum", - ); -} - -#[bench] -fn lex_regex(b: &mut Bencher) { - bench_simple( - b, - "/Lorem/;/Ipsum/;/is/;/simply/;/dummy/;/text/;/of/;/the/;/printing;and/;/typesetting;\ - industry;Lorem;Ipsum/;has;been/;the;industry/;s;standard/;/dummy;text/;", - ); -} - -#[bench] -fn lex_long_ident(b: &mut Bencher) { - bench_simple( - b, - "LoremIpsumissimplydummytextoftheprintingandtypesettingindustryLoremIpsum\ - hasbeentheindustrysstandarddummytexteversincethe1500swhenanunknownprinter\ - tookagalleyoftypeandscrambledittomakeatypespecimenbookIthassurvivednot\ - onlyfivecenturiesbutalsotheleapintoelectronictypesettingremainingessentially\ - unchangedItwaspopularisedinthe1960swiththereleaseofLetrasetsheetscontaining\ - LoremIpsumpassagesandmorerecentlywithdesktoppublishingsoftwarelikeAldus\ - PageMakerincludingversionsofLoremIpsum", - ); -} - -#[bench] -fn lex_semicolons(b: &mut Bencher) { - bench_simple( - b, - ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\ - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\ - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\ - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\ - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\ - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\ - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\ - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\ - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\ - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\ - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\ - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\ - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\ - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\ - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\ - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\ - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\ - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\ - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\ - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\ - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;", - ); -} - -#[test] -fn issue_1272_1_ts() { - let (tokens, errors) = lex_errors(crate::Syntax::Typescript(Default::default()), "\\u{16}"); - assert_eq!(tokens.len(), 1); - assert_ne!(errors, Vec::new()); -} - -#[test] -fn issue_1272_1_js() { - let (tokens, errors) = lex_errors(crate::Syntax::Es(Default::default()), "\\u{16}"); - assert_eq!(tokens.len(), 1); - assert_ne!(errors, Vec::new()); -} - -#[test] -fn issue_1272_2_ts() { - // Not recoverable yet - let (tokens, errors) = lex_errors(crate::Syntax::Typescript(Default::default()), "\u{16}"); - assert_eq!(tokens.len(), 1); - assert_eq!(errors, Vec::new()); -} - -#[test] -fn issue_1272_2_js() { - // Not recoverable yet - let (tokens, errors) = lex_errors(crate::Syntax::Es(Default::default()), "\u{16}"); - assert_eq!(tokens.len(), 1); - assert_eq!(errors, Vec::new()); -} - -#[test] -fn issue_2853_1_js() { - let (tokens, errors) = lex_errors(crate::Syntax::Es(Default::default()), "const a = \"\\0a\""); - - assert_eq!(errors, Vec::new()); - assert_eq!( - tokens, - vec![ - Word(Word::Keyword(Keyword::Const)), - Word(Word::Ident("a".into())), - Token::AssignOp(AssignOp::Assign), - Token::Str { - value: atom!("\u{0000}a").into(), - raw: atom!("\"\\0a\""), - } - ], - ); -} - -#[test] -fn issue_2853_2_ts() { - let (tokens, errors) = lex_errors( - crate::Syntax::Typescript(Default::default()), - "const a = \"\\0a\"", - ); - - assert_eq!(errors, Vec::new()); - assert_eq!( - tokens, - vec![ - Word(Word::Keyword(Keyword::Const)), - Word(Word::Ident("a".into())), - Token::AssignOp(AssignOp::Assign), - Token::Str { - value: atom!("\u{0000}a").into(), - raw: atom!("\"\\0a\""), - } - ], - ); -} - -#[test] -fn issue_2853_3_js() { - let (tokens, errors) = lex_errors( - crate::Syntax::Es(Default::default()), - "const a = \"\u{0000}a\"", - ); - - assert_eq!(errors, Vec::new()); - assert_eq!( - tokens, - vec![ - Word(Word::Keyword(Keyword::Const)), - Word(Word::Ident("a".into())), - Token::AssignOp(AssignOp::Assign), - Token::Str { - value: atom!("\u{0000}a").into(), - raw: atom!("\"\u{0000}a\""), - } - ], - ); -} - -#[test] -fn issue_2853_4_ts() { - let (tokens, errors) = lex_errors( - crate::Syntax::Typescript(Default::default()), - "const a = \"\u{0000}a\"", - ); - - assert_eq!(errors, Vec::new()); - assert_eq!( - tokens, - vec![ - Word(Word::Keyword(Keyword::Const)), - Word(Word::Ident("a".into())), - Token::AssignOp(AssignOp::Assign), - Token::Str { - value: atom!("\u{0000}a").into(), - raw: atom!("\"\u{0000}a\""), - } - ], - ); -} - -#[test] -fn issue_2853_5_jsx() { - let (tokens, errors) = lex_errors( - crate::Syntax::Es(EsSyntax { - jsx: true, - ..Default::default() - }), - "const a = \"\\0a\"", - ); - - assert_eq!(errors, Vec::new()); - assert_eq!( - tokens, - vec![ - Word(Word::Keyword(Keyword::Const)), - Word(Word::Ident("a".into())), - Token::AssignOp(AssignOp::Assign), - Token::Str { - value: atom!("\u{0000}a").into(), - raw: atom!("\"\\0a\""), - } - ] - ); -} - -#[test] -fn issue_2853_6_tsx() { - let (tokens, errors) = lex_errors( - crate::Syntax::Typescript(crate::TsSyntax { - tsx: true, - ..Default::default() - }), - "const a = \"\\0a\"", - ); - - assert_eq!(errors, Vec::new()); - assert_eq!( - tokens, - vec![ - Word(Word::Keyword(Keyword::Const)), - Word(Word::Ident("a".into())), - Token::AssignOp(AssignOp::Assign), - Token::Str { - value: atom!("\u{0000}a").into(), - raw: atom!("\"\\0a\""), - } - ] - ); -} - -#[test] -fn issue_2853_7_jsx() { - let (tokens, errors) = lex_errors( - crate::Syntax::Es(EsSyntax { - jsx: true, - ..Default::default() - }), - "const a = \"\u{0000}a\"", - ); - - assert_eq!(errors, Vec::new()); - assert_eq!( - tokens, - vec![ - Word(Word::Keyword(Keyword::Const)), - Word(Word::Ident("a".into())), - Token::AssignOp(AssignOp::Assign), - Token::Str { - value: atom!("\u{0000}a").into(), - raw: atom!("\"\u{0000}a\""), - } - ] - ); -} - -#[test] -fn issue_2853_8_tsx() { - let (tokens, errors) = lex_errors( - crate::Syntax::Typescript(crate::TsSyntax { - tsx: true, - ..Default::default() - }), - "const a = \"\u{0000}a\"", - ); - - assert_eq!(errors, Vec::new()); - assert_eq!( - tokens, - vec![ - Word(Word::Keyword(Keyword::Const)), - Word(Word::Ident("a".into())), - Token::AssignOp(AssignOp::Assign), - Token::Str { - value: atom!("\u{0000}a").into(), - raw: atom!("\"\u{0000}a\""), - } - ] - ); -} - -#[test] -#[ignore = "Raw token should be different. See https://github.com/denoland/deno/issues/9620"] -fn normalize_tpl_carriage_return() { - assert_eq!( - lex_tokens(Syntax::default(), "`\r\n`"), - lex_tokens(Syntax::default(), "`\n`") - ); - assert_eq!( - lex_tokens(Syntax::default(), "`\r`"), - lex_tokens(Syntax::default(), "`\n`") - ); -} - -#[test] -fn conflict_marker_trivia1() { - let (_, errors) = lex_errors( - Default::default(), - r#" -class C { -<<<<<<< HEAD - v = 1; -======= - v = 2; ->>>>>>> Branch-a -} -"#, - ); - - assert_eq!(errors.len(), 3); - assert!(errors.iter().all(|e| e.kind() == &SyntaxError::TS1185)); -} - -#[test] -fn conflict_marker_trivia2() { - let (_, errors) = lex_errors( - crate::Syntax::Typescript(Default::default()), - r#" -class C { - foo() { -<<<<<<< B - a(); - } -======= - b(); - } ->>>>>>> A - - public bar() { } -} -"#, - ); - - assert_eq!(errors.len(), 3); - assert!(errors.iter().all(|e| e.kind() == &SyntaxError::TS1185)); -} - -#[test] -fn conflict_marker_trivia3() { - let (_, errors) = lex_errors( - crate::Syntax::Typescript(crate::TsSyntax { - tsx: true, - ..Default::default() - }), - r#" -const x =
-<<<<<<< HEAD -"#, - ); - - assert_eq!(errors.len(), 1); - assert!(errors.iter().all(|e| e.kind() == &SyntaxError::TS1185)); -} - -#[test] -fn conflict_marker_trivia4() { - let (_, errors) = lex_errors( - crate::Syntax::Typescript(Default::default()), - r#" -const x =
-<<<<<<< HEAD -"#, - ); - - assert_eq!(errors.len(), 1); - assert!(errors.iter().all(|e| e.kind() == &SyntaxError::TS1185)); -} - -#[test] -fn conflict_marker_diff3_trivia1() { - let (_, errors) = lex_errors( - crate::Syntax::Typescript(Default::default()), - r#" -class C { -<<<<<<< HEAD - v = 1; -||||||| merged common ancestors - v = 3; -======= - v = 2; ->>>>>>> Branch-a -} -"#, - ); - - assert_eq!(errors.len(), 4); - assert!(errors.iter().all(|e| e.kind() == &SyntaxError::TS1185)); -} - -#[test] -fn conflict_marker_diff3_trivia2() { - let (_, errors) = lex_errors( - crate::Syntax::Typescript(Default::default()), - r#" -class C { - foo() { -<<<<<<< B - a(); - } -||||||| merged common ancestors - c(); - } -======= - b(); - } ->>>>>>> A - - public bar() { } -} -"#, - ); - - assert_eq!(errors.len(), 4); - assert!(errors.iter().all(|e| e.kind() == &SyntaxError::TS1185)); -} - -#[test] -fn issue_9106() { - assert_eq!( - lex_tokens( - crate::Syntax::Es(EsSyntax { - jsx: true, - ..Default::default() - }), - "\n\r\nABC;" - ), - vec![ - Token::JSXTagStart, - Token::JSXName { - name: atom!("Page") - }, - JSXTagEnd, - JSXText { - raw: atom!("\n\r\nABC"), - value: atom!("\n\nABC"), - }, - JSXTagStart, - tok!('/'), - JSXName { - name: atom!("Page") - }, - JSXTagEnd, - Semi, - ] - ); -} diff --git a/crates/swc_ecma_lexer/src/lib.rs b/crates/swc_ecma_lexer/src/lib.rs deleted file mode 100644 index 38a134c111e2..000000000000 --- a/crates/swc_ecma_lexer/src/lib.rs +++ /dev/null @@ -1,396 +0,0 @@ -//! # swc_ecma_lexer -//! -//! This crate provides a lexer for ECMAScript and TypeScript. It can ensure -//! these tokens are correctly parsed. - -#![cfg_attr(docsrs, feature(doc_cfg))] -#![cfg_attr(test, feature(test))] -#![deny(clippy::all)] -#![deny(unused)] -#![allow(clippy::nonminimal_bool)] -#![allow(clippy::too_many_arguments)] -#![allow(clippy::unnecessary_unwrap)] -#![allow(clippy::vec_box)] -#![allow(clippy::wrong_self_convention)] -#![allow(clippy::match_like_matches_macro)] -#![allow(unexpected_cfgs)] - -pub mod common; -pub mod lexer; -mod parser; -#[macro_use] -pub mod token; -pub mod error; -pub mod input; -mod utils; - -use common::parser::{buffer::Buffer, Parser as ParserTrait}; -pub use swc_common::input::StringInput; - -use self::common::{context::Context, parser::PResult}; -pub use self::{ - common::syntax::{EsSyntax, Syntax, SyntaxFlags, TsSyntax}, - input::Capturing, - lexer::{Lexer, TokenContext, TokenContexts, TokenType}, - parser::Parser, -}; - -#[cfg(test)] -fn with_test_sess(src: &str, f: F) -> Result -where - F: FnOnce(&swc_common::errors::Handler, swc_common::input::StringInput<'_>) -> Result, -{ - use swc_common::FileName; - - ::testing::run_test(false, |cm, handler| { - let fm = cm.new_source_file(FileName::Real("testing".into()).into(), src.to_string()); - - f(handler, (&*fm).into()) - }) -} - -#[macro_export] -macro_rules! tok { - ('`') => { - $crate::token::Token::BackQuote - }; - // (';') => { Token::Semi }; - ('@') => { - $crate::token::Token::At - }; - ('#') => { - $crate::token::Token::Hash - }; - - ('&') => { - $crate::token::Token::BinOp($crate::token::BinOpToken::BitAnd) - }; - ('|') => { - $crate::token::Token::BinOp($crate::token::BinOpToken::BitOr) - }; - ('^') => { - $crate::token::Token::BinOp($crate::token::BinOpToken::BitXor) - }; - ('+') => { - $crate::token::Token::BinOp($crate::token::BinOpToken::Add) - }; - ('-') => { - $crate::token::Token::BinOp($crate::token::BinOpToken::Sub) - }; - ("??") => { - $crate::token::Token::BinOp($crate::token::BinOpToken::NullishCoalescing) - }; - ('~') => { - $crate::token::Token::Tilde - }; - ('!') => { - $crate::token::Token::Bang - }; - ("&&") => { - $crate::token::Token::BinOp($crate::token::BinOpToken::LogicalAnd) - }; - ("||") => { - $crate::token::Token::BinOp($crate::token::BinOpToken::LogicalOr) - }; - ("&&=") => { - $crate::token::Token::AssignOp(swc_ecma_ast::AssignOp::AndAssign) - }; - ("||=") => { - $crate::token::Token::AssignOp(swc_ecma_ast::AssignOp::OrAssign) - }; - ("??=") => { - $crate::token::Token::AssignOp(swc_ecma_ast::AssignOp::NullishAssign) - }; - - ("==") => { - $crate::token::Token::BinOp($crate::token::BinOpToken::EqEq) - }; - ("===") => { - $crate::token::Token::BinOp($crate::token::BinOpToken::EqEqEq) - }; - ("!=") => { - $crate::token::Token::BinOp($crate::token::BinOpToken::NotEq) - }; - ("!==") => { - $crate::token::Token::BinOp($crate::token::BinOpToken::NotEqEq) - }; - - (',') => { - $crate::token::Token::Comma - }; - ('?') => { - $crate::token::Token::QuestionMark - }; - (':') => { - $crate::token::Token::Colon - }; - ('.') => { - $crate::token::Token::Dot - }; - ("=>") => { - $crate::token::Token::Arrow - }; - ("...") => { - $crate::token::Token::DotDotDot - }; - ("${") => { - $crate::token::Token::DollarLBrace - }; - - ('+') => { - $crate::token::Token::BinOp($crate::token::BinOpToken::Add) - }; - ('-') => { - $crate::token::Token::BinOp($crate::token::BinOpToken::Sub) - }; - ('*') => { - $crate::token::Token::BinOp($crate::token::BinOpToken::Mul) - }; - ('/') => { - $crate::token::Token::BinOp($crate::token::BinOpToken::Div) - }; - ("/=") => { - $crate::token::Token::AssignOp(swc_ecma_ast::AssignOp::DivAssign) - }; - ('%') => { - $crate::token::Token::BinOp($crate::token::BinOpToken::Mod) - }; - ('~') => { - $crate::token::Token::Tilde - }; - ('<') => { - $crate::token::Token::BinOp($crate::token::BinOpToken::Lt) - }; - ("<<") => { - $crate::token::Token::BinOp($crate::token::BinOpToken::LShift) - }; - ("<=") => { - $crate::token::Token::BinOp($crate::token::BinOpToken::LtEq) - }; - ("<<=") => { - $crate::token::Token::AssignOp($crate::token::AssignOp::LShiftAssign) - }; - ('>') => { - $crate::token::Token::BinOp($crate::token::BinOpToken::Gt) - }; - (">>") => { - $crate::token::Token::BinOp($crate::token::BinOpToken::RShift) - }; - (">>>") => { - $crate::token::Token::BinOp($crate::token::BinOpToken::ZeroFillRShift) - }; - (">=") => { - $crate::token::Token::BinOp($crate::token::BinOpToken::GtEq) - }; - (">>=") => { - $crate::token::Token::AssignOp(swc_ecma_ast::AssignOp::RShiftAssign) - }; - (">>>=") => { - $crate::token::Token::AssignOp(swc_ecma_ast::AssignOp::ZeroFillRShiftAssign) - }; - - ("++") => { - $crate::token::Token::PlusPlus - }; - ("--") => { - $crate::token::Token::MinusMinus - }; - - ('=') => { - $crate::token::Token::AssignOp(swc_ecma_ast::AssignOp::Assign) - }; - - ('(') => { - $crate::token::Token::LParen - }; - (')') => { - $crate::token::Token::RParen - }; - ('{') => { - $crate::token::Token::LBrace - }; - ('}') => { - $crate::token::Token::RBrace - }; - ('[') => { - $crate::token::Token::LBracket - }; - (']') => { - $crate::token::Token::RBracket - }; - - ("await") => { - $crate::token::Token::Word($crate::token::Word::Keyword($crate::token::Keyword::Await)) - }; - ("break") => { - $crate::token::Token::Word($crate::token::Word::Keyword($crate::token::Keyword::Break)) - }; - ("case") => { - $crate::token::Token::Word($crate::token::Word::Keyword($crate::token::Keyword::Case)) - }; - ("catch") => { - $crate::token::Token::Word($crate::token::Word::Keyword($crate::token::Keyword::Catch)) - }; - ("class") => { - $crate::token::Token::Word($crate::token::Word::Keyword($crate::token::Keyword::Class)) - }; - ("const") => { - $crate::token::Token::Word($crate::token::Word::Keyword($crate::token::Keyword::Const)) - }; - ("continue") => { - $crate::token::Token::Word($crate::token::Word::Keyword( - $crate::token::Keyword::Continue, - )) - }; - ("debugger") => { - $crate::token::Token::Word($crate::token::Word::Keyword( - $crate::token::Keyword::Debugger, - )) - }; - ("default") => { - $crate::token::Token::Word($crate::token::Word::Keyword( - $crate::token::Keyword::Default_, - )) - }; - ("delete") => { - $crate::token::Token::Word($crate::token::Word::Keyword($crate::token::Keyword::Delete)) - }; - ("do") => { - $crate::token::Token::Word($crate::token::Word::Keyword($crate::token::Keyword::Do)) - }; - ("else") => { - $crate::token::Token::Word($crate::token::Word::Keyword($crate::token::Keyword::Else)) - }; - ("export") => { - $crate::token::Token::Word($crate::token::Word::Keyword($crate::token::Keyword::Export)) - }; - ("extends") => { - $crate::token::Token::Word($crate::token::Word::Keyword( - $crate::token::Keyword::Extends, - )) - }; - ("false") => { - $crate::token::Token::Word($crate::token::Word::False) - }; - ("finally") => { - $crate::token::Token::Word($crate::token::Word::Keyword( - $crate::token::Keyword::Finally, - )) - }; - ("for") => { - $crate::token::Token::Word($crate::token::Word::Keyword($crate::token::Keyword::For)) - }; - ("function") => { - $crate::token::Token::Word($crate::token::Word::Keyword( - $crate::token::Keyword::Function, - )) - }; - ("if") => { - $crate::token::Token::Word($crate::token::Word::Keyword($crate::token::Keyword::If)) - }; - ("in") => { - $crate::token::Token::Word($crate::token::Word::Keyword($crate::token::Keyword::In)) - }; - ("instanceof") => { - $crate::token::Token::Word($crate::token::Word::Keyword( - $crate::token::Keyword::InstanceOf, - )) - }; - ("import") => { - $crate::token::Token::Word($crate::token::Word::Keyword($crate::token::Keyword::Import)) - }; - ("let") => { - $crate::token::Token::Word($crate::token::Word::Keyword($crate::token::Keyword::Let)) - }; - ("new") => { - $crate::token::Token::Word($crate::token::Word::Keyword($crate::token::Keyword::New)) - }; - ("null") => { - $crate::token::Token::Word($crate::token::Word::Null) - }; - - ("return") => { - $crate::token::Token::Word($crate::token::Word::Keyword($crate::token::Keyword::Return)) - }; - ("super") => { - $crate::token::Token::Word($crate::token::Word::Keyword($crate::token::Keyword::Super)) - }; - ("switch") => { - $crate::token::Token::Word($crate::token::Word::Keyword($crate::token::Keyword::Switch)) - }; - ("this") => { - $crate::token::Token::Word($crate::token::Word::Keyword($crate::token::Keyword::This)) - }; - ("throw") => { - $crate::token::Token::Word($crate::token::Word::Keyword($crate::token::Keyword::Throw)) - }; - ("true") => { - $crate::token::Token::Word($crate::token::Word::True) - }; - ("try") => { - $crate::token::Token::Word($crate::token::Word::Keyword($crate::token::Keyword::Try)) - }; - ("typeof") => { - $crate::token::Token::Word($crate::token::Word::Keyword($crate::token::Keyword::TypeOf)) - }; - ("var") => { - $crate::token::Token::Word($crate::token::Word::Keyword($crate::token::Keyword::Var)) - }; - ("void") => { - $crate::token::Token::Word($crate::token::Word::Keyword($crate::token::Keyword::Void)) - }; - ("while") => { - $crate::token::Token::Word($crate::token::Word::Keyword($crate::token::Keyword::While)) - }; - ("with") => { - $crate::token::Token::Word($crate::token::Word::Keyword($crate::token::Keyword::With)) - }; - ("yield") => { - $crate::token::Token::Word($crate::token::Word::Keyword($crate::token::Keyword::Yield)) - }; - - // ---------- - // JSX - // ---------- - (JSXTagStart) => { - $crate::token::Token::JSXTagStart - }; - - (JSXTagEnd) => { - $crate::token::Token::JSXTagEnd - }; - - ($tt:tt) => { - $crate::token::Token::Word($crate::token::Word::Ident($crate::token::IdentLike::Known( - $crate::token::known_ident!($tt), - ))) - }; -} - -#[inline(always)] -#[cfg(any( - target_arch = "wasm32", - target_arch = "arm", - not(feature = "stacker"), - // miri does not work with stacker - miri -))] -fn maybe_grow R>(_red_zone: usize, _stack_size: usize, callback: F) -> R { - callback() -} - -#[inline(always)] -#[cfg(all( - not(any(target_arch = "wasm32", target_arch = "arm", miri)), - feature = "stacker" -))] -fn maybe_grow R>(red_zone: usize, stack_size: usize, callback: F) -> R { - stacker::maybe_grow(red_zone, stack_size, callback) -} - -pub fn lexer(input: Lexer) -> PResult> { - let capturing = input::Capturing::new(input); - let mut parser = parser::Parser::new_from(capturing); - let _ = parser.parse_module()?; - Ok(parser.input_mut().iter_mut().take()) -} diff --git a/crates/swc_ecma_lexer/src/parser/macros.rs b/crates/swc_ecma_lexer/src/parser/macros.rs deleted file mode 100644 index 5079a5350eed..000000000000 --- a/crates/swc_ecma_lexer/src/parser/macros.rs +++ /dev/null @@ -1,8 +0,0 @@ -macro_rules! trace_cur { - ($p:expr, $name:ident) => {{ - if cfg!(feature = "debug") { - #[cfg(debug_assertions)] - tracing::debug!("{}: {:?}", stringify!($name), $p.input.cur()); - } - }}; -} diff --git a/crates/swc_ecma_lexer/src/parser/mod.rs b/crates/swc_ecma_lexer/src/parser/mod.rs deleted file mode 100644 index 51481a5f1eab..000000000000 --- a/crates/swc_ecma_lexer/src/parser/mod.rs +++ /dev/null @@ -1,294 +0,0 @@ -#![allow(clippy::let_unit_value)] -#![deny(non_snake_case)] - -use swc_ecma_ast::*; - -use crate::{ - common::{ - input::Tokens, - parser::{ - buffer::Buffer as BufferTrait, - expr::{parse_lhs_expr, parse_primary_expr, parse_tagged_tpl, parse_unary_expr}, - jsx::parse_jsx_element, - module_item::parse_module_item_block_body, - parse_shebang, - stmt::parse_stmt_block_body, - typescript::ts_in_no_context, - Parser as ParserTrait, - }, - }, - error::Error, - input::Buffer, - token::{Token, TokenAndSpan}, - Context, *, -}; - -#[macro_use] -mod macros; -#[cfg(feature = "typescript")] -mod typescript; - -/// EcmaScript parser. -#[derive(Clone)] -pub struct Parser> { - state: crate::common::parser::state::State, - input: Buffer, - found_module_item: bool, -} - -impl<'a, I: Tokens> crate::common::parser::Parser<'a> for Parser { - type Buffer = Buffer; - type Checkpoint = Self; - type I = I; - type Next = TokenAndSpan; - type Token = Token; - type TokenAndSpan = TokenAndSpan; - - #[inline(always)] - fn input(&self) -> &Self::Buffer { - &self.input - } - - #[inline(always)] - fn input_mut(&mut self) -> &mut Self::Buffer { - &mut self.input - } - - #[inline(always)] - fn state(&self) -> &common::parser::state::State { - &self.state - } - - #[inline(always)] - fn state_mut(&mut self) -> &mut common::parser::state::State { - &mut self.state - } - - fn checkpoint_save(&self) -> Self::Checkpoint { - self.clone() - } - - fn checkpoint_load(&mut self, checkpoint: Self::Checkpoint) { - *self = checkpoint; - } - - #[inline(always)] - fn mark_found_module_item(&mut self) { - self.found_module_item = true; - } - - #[inline(always)] - fn parse_unary_expr(&mut self) -> PResult> { - parse_unary_expr(self) - } - - #[inline(always)] - fn parse_jsx_element( - &mut self, - _in_expr_context: bool, - ) -> PResult> { - parse_jsx_element(self) - } - - #[inline(always)] - fn parse_primary_expr(&mut self) -> PResult> { - parse_primary_expr(self) - } - - #[inline(always)] - fn ts_in_no_context(&mut self, op: impl FnOnce(&mut Self) -> PResult) -> PResult { - ts_in_no_context(self, op) - } - - fn parse_tagged_tpl( - &mut self, - tag: Box, - type_params: Option>, - ) -> PResult { - parse_tagged_tpl(self, tag, type_params) - } - - fn parse_tagged_tpl_ty(&mut self) -> PResult { - unreachable!("use `common::parser::expr::parse_ts_tpl_lit_type` directly"); - } - - fn parse_lhs_expr(&mut self) -> PResult> { - parse_lhs_expr::(self) - } -} - -impl> Parser { - pub fn new_from(mut input: I) -> Self { - let in_declare = input.syntax().dts(); - let mut ctx = input.ctx() | Context::TopLevel; - ctx.set(Context::InDeclare, in_declare); - input.set_ctx(ctx); - - let mut p = Parser { - state: Default::default(), - input: Buffer::new(input), - found_module_item: false, - }; - p.input.bump(); // consume EOF - p - } - - pub fn take_errors(&mut self) -> Vec { - self.input.iter.take_errors() - } - - pub fn take_script_module_errors(&mut self) -> Vec { - self.input.iter.take_script_module_errors() - } - - pub fn parse_script(&mut self) -> PResult