diff --git a/bindings/binding_core_node/src/bundle.rs b/bindings/binding_core_node/src/bundle.rs index c2378b1698..2fe29702c9 100644 --- a/bindings/binding_core_node/src/bundle.rs +++ b/bindings/binding_core_node/src/bundle.rs @@ -129,16 +129,16 @@ impl Task for BundleTask { None, None, true, - codegen_target, SourceMapsConfig::Bool(true), // TODO &Default::default(), None, - minify, None, true, - false, Default::default(), + swc_core::ecma::codegen::Config::default() + .with_target(codegen_target) + .with_minify(minify), )?; Ok((k, output)) diff --git a/bindings/binding_core_node/src/print.rs b/bindings/binding_core_node/src/print.rs index 983cad30e1..720c23e57f 100644 --- a/bindings/binding_core_node/src/print.rs +++ b/bindings/binding_core_node/src/print.rs @@ -40,18 +40,18 @@ impl Task for PrintTask { None, options.output_path.clone(), true, - options.config.jsc.target.unwrap_or(EsVersion::Es2020), options .source_maps .clone() .unwrap_or(SourceMapsConfig::Bool(false)), &Default::default(), None, - options.config.minify.into_bool(), None, options.config.emit_source_map_columns.into_bool(), - false, Default::default(), + swc_core::ecma::codegen::Config::default() + .with_target(options.config.jsc.target.unwrap_or(EsVersion::Es2020)) + .with_minify(options.config.minify.into_bool()), ) .convert_err() }) @@ -102,18 +102,18 @@ pub fn print_sync(program: String, options: Buffer) -> napi::Result, m: &Module, minify: bool) -> Result } let mut e = swc_ecma_codegen::Emitter { - cfg: swc_ecma_codegen::Config { - minify, - ..Default::default() - }, + cfg: swc_ecma_codegen::Config::default().with_minify(true), cm, comments: None, wr, diff --git a/crates/swc/benches/typescript.rs b/crates/swc/benches/typescript.rs index cde28c9da5..b87bfc8ccb 100644 --- a/crates/swc/benches/typescript.rs +++ b/crates/swc/benches/typescript.rs @@ -115,15 +115,13 @@ fn bench_codegen(b: &mut Bencher, _target: EsVersion) { None, None, false, - EsVersion::Es2020, SourceMapsConfig::Bool(false), &Default::default(), None, - false, None, false, - false, Default::default(), + swc_ecma_codegen::Config::default().with_target(EsVersion::Es2020), ) .unwrap() })); diff --git a/crates/swc/src/builder.rs b/crates/swc/src/builder.rs index 45fbaa764b..e335ee6fd3 100644 --- a/crates/swc/src/builder.rs +++ b/crates/swc/src/builder.rs @@ -187,8 +187,8 @@ impl<'a, 'b, P: swc_ecma_visit::Fold> PassBuilder<'a, 'b, P> { (true, c.config.import_interop(), c.config.ignore_dynamic) } Some(ModuleConfig::SystemJs(_)) - | Some(ModuleConfig::Es6) - | Some(ModuleConfig::NodeNext) + | Some(ModuleConfig::Es6(..)) + | Some(ModuleConfig::NodeNext(..)) | None => (false, true.into(), true), }; @@ -233,15 +233,18 @@ impl<'a, 'b, P: swc_ecma_visit::Fold> PassBuilder<'a, 'b, P> { should_enable(self.target, EsVersion::Es2021) ), Optional::new( - compat::es2020::es2020(compat::es2020::Config { - nullish_coalescing: compat::es2020::nullish_coalescing::Config { - no_document_all: assumptions.no_document_all + compat::es2020::es2020( + compat::es2020::Config { + nullish_coalescing: compat::es2020::nullish_coalescing::Config { + no_document_all: assumptions.no_document_all + }, + optional_chaining: compat::es2020::optional_chaining::Config { + no_document_all: assumptions.no_document_all, + pure_getter: assumptions.pure_getters + } }, - optional_chaining: compat::es2020::optional_chaining::Config { - no_document_all: assumptions.no_document_all, - pure_getter: assumptions.pure_getters - } - }), + self.unresolved_mark + ), should_enable(self.target, EsVersion::Es2020) ), Optional::new( diff --git a/crates/swc/src/config/mod.rs b/crates/swc/src/config/mod.rs index 38f8bae512..1a79edfdf0 100644 --- a/crates/swc/src/config/mod.rs +++ b/crates/swc/src/config/mod.rs @@ -10,7 +10,7 @@ use std::{ usize, }; -use anyhow::{bail, Error}; +use anyhow::{bail, Context, Error}; use dashmap::DashMap; use either::Either; use indexmap::IndexMap; @@ -54,10 +54,13 @@ use swc_ecma_parser::{parse_file_as_expr, Syntax, TsConfig}; use swc_ecma_transforms::{ feature::FeatureFlag, hygiene, modules, - modules::{path::NodeImportResolver, rewriter::import_rewriter}, + modules::{path::NodeImportResolver, rewriter::import_rewriter, EsModuleConfig}, optimization::{const_modules, json_parse, simplifier}, pass::{noop, Optional}, - proposals::{decorators, export_default_from, import_assertions}, + proposals::{ + decorators, explicit_resource_management::explicit_resource_management, + export_default_from, import_assertions, + }, react::{self, default_pragma, default_pragma_frag}, resolver, typescript::{self, TsEnumConfig, TsImportExportAssignConfig}, @@ -324,7 +327,7 @@ impl Options { config: Option, comments: Option<&'a SingleThreadedComments>, custom_before_pass: impl FnOnce(&Program) -> P, - ) -> Result, Error> + ) -> Result>, Error> where P: 'a + swc_ecma_visit::Fold, { @@ -416,7 +419,7 @@ impl Options { if matches!( cfg.module, - None | Some(ModuleConfig::Es6 | ModuleConfig::NodeNext) + None | Some(ModuleConfig::Es6(..) | ModuleConfig::NodeNext(..)) ) { c.module = true; } @@ -527,7 +530,7 @@ impl Options { .as_ref() .map(|v| match v.format.comments.clone().into_inner() { Some(v) => v, - None => BoolOr::Bool(false), + None => BoolOr::Bool(true), }) .unwrap_or_else(|| { BoolOr::Data(if cfg.minify.into_bool() { @@ -606,11 +609,11 @@ impl Options { ); let import_export_assign_config = match cfg.module { - Some(ModuleConfig::Es6) => TsImportExportAssignConfig::EsNext, + Some(ModuleConfig::Es6(..)) => TsImportExportAssignConfig::EsNext, Some(ModuleConfig::CommonJs(..)) | Some(ModuleConfig::Amd(..)) | Some(ModuleConfig::Umd(..)) => TsImportExportAssignConfig::Preserve, - Some(ModuleConfig::NodeNext) => TsImportExportAssignConfig::NodeNext, + Some(ModuleConfig::NodeNext(..)) => TsImportExportAssignConfig::NodeNext, // TODO: should Preserve for SystemJS _ => TsImportExportAssignConfig::Classic, }; @@ -664,7 +667,7 @@ impl Options { comments.map(|v| v as _), ); - let keep_import_assertions = experimental.keep_import_assertions.into_bool(); + let keep_import_attributes = experimental.keep_import_attributes.into_bool(); #[cfg(feature = "plugin")] let plugin_transforms = { @@ -759,88 +762,99 @@ impl Options { noop() }; - let pass = chain!( - lint_to_fold(swc_ecma_lints::rules::all(LintParams { - program: &program, - lint_config: &lints, - top_level_ctxt, - unresolved_ctxt, - es_version, - source_map: cm.clone(), - })), - // Decorators may use type information - Optional::new( - match transform.decorator_version.unwrap_or_default() { - DecoratorVersion::V202112 => { - Either::Left(decorators(decorators::Config { - legacy: transform.legacy_decorator.into_bool(), - emit_metadata: transform.decorator_metadata.into_bool(), - use_define_for_class_fields: !assumptions.set_public_class_fields, - })) - } - DecoratorVersion::V202203 => { - Either::Right( + let pass: Box = if experimental + .disable_builtin_transforms_for_internal_testing + .into_bool() + { + Box::new(plugin_transforms) + } else { + Box::new(chain!( + lint_to_fold(swc_ecma_lints::rules::all(LintParams { + program: &program, + lint_config: &lints, + top_level_ctxt, + unresolved_ctxt, + es_version, + source_map: cm.clone(), + })), + // Decorators may use type information + Optional::new( + match transform.decorator_version.unwrap_or_default() { + DecoratorVersion::V202112 => { + Either::Left(decorators(decorators::Config { + legacy: transform.legacy_decorator.into_bool(), + emit_metadata: transform.decorator_metadata.into_bool(), + use_define_for_class_fields: !assumptions.set_public_class_fields, + })) + } + DecoratorVersion::V202203 => { + Either::Right( swc_ecma_transforms::proposals::decorator_2022_03::decorator_2022_03(), ) - } - }, - syntax.decorators() - ), - // The transform strips import assertions, so it's only enabled if - // keep_import_assertions is false. - Optional::new(import_assertions(), !keep_import_assertions), - Optional::new( - typescript::strip_with_jsx::>( - cm.clone(), - typescript::Config { - pragma: Some( - transform - .react - .pragma - .clone() - .unwrap_or_else(default_pragma) - ), - pragma_frag: Some( - transform - .react - .pragma_frag - .clone() - .unwrap_or_else(default_pragma_frag) - ), - ts_enum_config: TsEnumConfig { - treat_const_enum_as_enum: transform - .treat_const_enum_as_enum - .into_bool(), - ts_enum_is_readonly: assumptions.ts_enum_is_readonly, - }, - import_export_assign_config, - ..Default::default() + } }, - comments.map(|v| v as _), - top_level_mark + syntax.decorators() ), - syntax.typescript() - ), - plugin_transforms, - custom_before_pass(&program), - // handle jsx - Optional::new( - react::react::<&dyn Comments>( - cm.clone(), - comments.map(|v| v as _), - transform.react, - top_level_mark, - unresolved_mark + Optional::new( + explicit_resource_management(), + syntax.explicit_resource_management() ), - syntax.jsx() - ), - pass, - Optional::new(jest::jest(), transform.hidden.jest.into_bool()), - Optional::new( - dropped_comments_preserver(comments.cloned()), - preserve_all_comments - ), - ); + // The transform strips import assertions, so it's only enabled if + // keep_import_assertions is false. + Optional::new(import_assertions(), !keep_import_attributes), + Optional::new( + typescript::strip_with_jsx::>( + cm.clone(), + typescript::Config { + pragma: Some( + transform + .react + .pragma + .clone() + .unwrap_or_else(default_pragma) + ), + pragma_frag: Some( + transform + .react + .pragma_frag + .clone() + .unwrap_or_else(default_pragma_frag) + ), + ts_enum_config: TsEnumConfig { + treat_const_enum_as_enum: transform + .treat_const_enum_as_enum + .into_bool(), + ts_enum_is_readonly: assumptions.ts_enum_is_readonly, + }, + import_export_assign_config, + ..Default::default() + }, + comments.map(|v| v as _), + top_level_mark + ), + syntax.typescript() + ), + plugin_transforms, + custom_before_pass(&program), + // handle jsx + Optional::new( + react::react::<&dyn Comments>( + cm.clone(), + comments.map(|v| v as _), + transform.react, + top_level_mark, + unresolved_mark + ), + syntax.jsx() + ), + pass, + Optional::new(jest::jest(), transform.hidden.jest.into_bool()), + Optional::new( + dropped_comments_preserver(comments.cloned()), + preserve_all_comments + ), + )) + }; Ok(BuiltInput { program, @@ -859,6 +873,9 @@ impl Options { preserve_comments, emit_source_map_columns: cfg.emit_source_map_columns.into_bool(), output: JscOutputConfig { charset, preamble }, + emit_assert_for_import_attributes: experimental + .emit_assert_for_import_attributes + .into_bool(), }) } } @@ -1231,6 +1248,9 @@ pub struct JsMinifyFormatOptions { /// Not implemented yet. #[serde(default, alias = "wrap_func_args")] pub wrap_func_args: bool, + + #[serde(default)] + pub emit_assert_for_import_attributes: bool, } fn default_comments() -> BoolOrDataConfig { @@ -1340,6 +1360,7 @@ impl Config { } /// One `BuiltConfig` per a directory with swcrc +#[non_exhaustive] pub struct BuiltInput { pub program: Program, pub pass: P, @@ -1363,6 +1384,37 @@ pub struct BuiltInput { pub emit_source_map_columns: bool, pub output: JscOutputConfig, + pub emit_assert_for_import_attributes: bool, +} + +impl

BuiltInput

+where + P: swc_ecma_visit::Fold, +{ + pub fn with_pass(self, map: impl FnOnce(P) -> N) -> BuiltInput + where + N: swc_ecma_visit::Fold, + { + BuiltInput { + program: self.program, + pass: map(self.pass), + syntax: self.syntax, + target: self.target, + minify: self.minify, + external_helpers: self.external_helpers, + source_maps: self.source_maps, + input_source_map: self.input_source_map, + is_module: self.is_module, + output_path: self.output_path, + source_file_name: self.source_file_name, + preserve_comments: self.preserve_comments, + inline_sources_content: self.inline_sources_content, + comments: self.comments, + emit_source_map_columns: self.emit_source_map_columns, + output: self.output, + emit_assert_for_import_attributes: self.emit_assert_for_import_attributes, + } + } } /// `jsc` in `.swcrc`. @@ -1446,7 +1498,10 @@ pub struct JscExperimental { pub plugins: Option>, /// If true, keeps import assertions in the output. #[serde(default)] - pub keep_import_assertions: BoolConfig, + pub keep_import_attributes: BoolConfig, + + #[serde(default)] + pub emit_assert_for_import_attributes: BoolConfig, /// Location where swc may stores its intermediate cache. /// Currently this is only being used for wasm plugin's bytecache. /// Path should be absolute directory, which will be created if not exist. @@ -1454,6 +1509,9 @@ pub struct JscExperimental { /// and will not be considered as breaking changes. #[serde(default)] pub cache_root: Option, + + #[serde(default)] + pub disable_builtin_transforms_for_internal_testing: BoolConfig, } #[derive(Debug, Clone, Copy, Serialize, Deserialize)] @@ -1513,9 +1571,9 @@ pub enum ModuleConfig { #[serde(rename = "systemjs")] SystemJs(modules::system_js::Config), #[serde(rename = "es6")] - Es6, + Es6(EsModuleConfig), #[serde(rename = "nodenext")] - NodeNext, + NodeNext(EsModuleConfig), } impl ModuleConfig { @@ -1538,11 +1596,20 @@ impl ModuleConfig { let skip_resolver = base_url.as_os_str().is_empty() && paths.is_empty(); match config { - None | Some(ModuleConfig::Es6) | Some(ModuleConfig::NodeNext) => { + None => { + if skip_resolver { + Box::new(noop()) + } else { + let resolver = build_resolver(base_url, paths, false); + + Box::new(import_rewriter(base, resolver)) + } + } + Some(ModuleConfig::Es6(config)) | Some(ModuleConfig::NodeNext(config)) => { if skip_resolver { Box::new(noop()) } else { - let resolver = build_resolver(base_url, paths); + let resolver = build_resolver(base_url, paths, config.resolve_fully); Box::new(import_rewriter(base, resolver)) } @@ -1556,7 +1623,7 @@ impl ModuleConfig { comments, )) } else { - let resolver = build_resolver(base_url, paths); + let resolver = build_resolver(base_url, paths, config.resolve_fully); Box::new(modules::common_js::common_js_with_resolver( resolver, base, @@ -1577,7 +1644,7 @@ impl ModuleConfig { comments, )) } else { - let resolver = build_resolver(base_url, paths); + let resolver = build_resolver(base_url, paths, config.config.resolve_fully); Box::new(modules::umd::umd_with_resolver( cm, @@ -1599,7 +1666,7 @@ impl ModuleConfig { comments, )) } else { - let resolver = build_resolver(base_url, paths); + let resolver = build_resolver(base_url, paths, config.config.resolve_fully); Box::new(modules::amd::amd_with_resolver( resolver, @@ -1615,7 +1682,7 @@ impl ModuleConfig { if skip_resolver { Box::new(modules::system_js::system_js(unresolved_mark, config)) } else { - let resolver = build_resolver(base_url, paths); + let resolver = build_resolver(base_url, paths, config.resolve_fully); Box::new(modules::system_js::system_js_with_resolver( resolver, @@ -1941,27 +2008,51 @@ fn default_env_name() -> String { } } -fn build_resolver(base_url: PathBuf, paths: CompiledPaths) -> Box { - static CACHE: Lazy> = +fn build_resolver( + mut base_url: PathBuf, + paths: CompiledPaths, + resolve_fully: bool, +) -> Box { + static CACHE: Lazy> = Lazy::new(Default::default); - if let Some(cached) = CACHE.get(&(base_url.clone(), paths.clone())) { + // On Windows, we need to normalize path as UNC path. + if cfg!(target_os = "windows") { + base_url = base_url + .canonicalize() + .with_context(|| { + format!( + "failed to canonicalize jsc.baseUrl(`{}`)\nThis is required on Windows \ + because of UNC path.", + base_url.display() + ) + }) + .unwrap(); + } + + if let Some(cached) = CACHE.get(&(base_url.clone(), paths.clone(), resolve_fully)) { return Box::new((*cached).clone()); } let r = { let r = TsConfigResolver::new( - NodeModulesResolver::new(Default::default(), Default::default(), true), + NodeModulesResolver::without_node_modules(Default::default(), Default::default(), true), base_url.clone(), paths.clone(), ); let r = CachingResolver::new(40, r); - let r = NodeImportResolver::with_base_dir(r, Some(base_url.clone())); + let r = NodeImportResolver::with_config( + r, + swc_ecma_transforms::modules::path::Config { + base_dir: Some(base_url.clone()), + resolve_fully, + }, + ); Arc::new(r) }; - CACHE.insert((base_url, paths), r.clone()); + CACHE.insert((base_url, paths, resolve_fully), r.clone()); Box::new(r) } diff --git a/crates/swc/src/lib.rs b/crates/swc/src/lib.rs index 00782b1b30..9843855f58 100644 --- a/crates/swc/src/lib.rs +++ b/crates/swc/src/lib.rs @@ -193,7 +193,7 @@ pub mod resolver { preserve_symlinks: bool, ) -> CachingResolver> { let r = TsConfigResolver::new( - NodeModulesResolver::new(target_env, alias, preserve_symlinks), + NodeModulesResolver::without_node_modules(target_env, alias, preserve_symlinks), base_url, paths, ); @@ -501,15 +501,13 @@ impl Compiler { source_file_name: Option<&str>, output_path: Option, inline_sources_content: bool, - target: EsVersion, source_map: SourceMapsConfig, source_map_names: &AHashMap, orig: Option<&sourcemap::SourceMap>, - minify: bool, comments: Option<&dyn Comments>, emit_source_map_columns: bool, - ascii_only: bool, preamble: &str, + codegen_config: swc_ecma_codegen::Config, ) -> Result where T: Node + VisitWith, @@ -535,17 +533,12 @@ impl Compiler { w.preamble(preamble).unwrap(); let mut wr = Box::new(w) as Box; - if minify { + if codegen_config.minify { wr = Box::new(swc_ecma_codegen::text_writer::omit_trailing_semi(wr)); } let mut emitter = Emitter { - cfg: swc_ecma_codegen::Config { - minify, - target, - ascii_only, - ..Default::default() - }, + cfg: codegen_config, comments, cm: self.cm.clone(), wr, @@ -681,10 +674,10 @@ impl SourceMapGenConfig for SwcSourceMapConfig<'_> { } fn skip(&self, f: &FileName) -> bool { - if let FileName::Custom(s) = f { - s.starts_with('<') - } else { - false + match f { + FileName::Internal(..) => true, + FileName::Custom(s) => s.starts_with('<'), + _ => false, } } } @@ -733,7 +726,7 @@ impl Compiler { } } - #[tracing::instrument(level = "info", skip_all)] + #[tracing::instrument(skip_all)] pub fn read_config(&self, opts: &Options, name: &FileName) -> Result, Error> { static CUR_DIR: Lazy = Lazy::new(|| { if cfg!(target_arch = "wasm32") { @@ -845,7 +838,7 @@ impl Compiler { /// This method handles merging of config. /// /// This method does **not** parse module. - #[tracing::instrument(level = "info", skip_all)] + #[tracing::instrument(skip_all)] pub fn parse_js_as_input<'a, P>( &'a self, fm: Lrc, @@ -908,7 +901,7 @@ impl Compiler { }) } - #[tracing::instrument(level = "info", skip_all)] + #[tracing::instrument(skip_all)] pub fn transform( &self, handler: &Handler, @@ -936,7 +929,7 @@ impl Compiler { /// /// This means, you can use `noop_visit_type`, `noop_fold_type` and /// `noop_visit_mut_type` in your visitor to reduce the binary size. - #[tracing::instrument(level = "info", skip_all)] + #[tracing::instrument(skip_all)] pub fn process_js_with_custom_pass( &self, fm: Arc, @@ -970,26 +963,9 @@ impl Compiler { } }; - let pass = chain!(config.pass, custom_after_pass(&config.program)); - - let config = BuiltInput { - program: config.program, - pass, - syntax: config.syntax, - target: config.target, - minify: config.minify, - external_helpers: config.external_helpers, - source_maps: config.source_maps, - input_source_map: config.input_source_map, - is_module: config.is_module, - output_path: config.output_path, - source_file_name: config.source_file_name, - preserve_comments: config.preserve_comments, - inline_sources_content: config.inline_sources_content, - comments: config.comments, - emit_source_map_columns: config.emit_source_map_columns, - output: config.output, - }; + let after_pass = custom_after_pass(&config.program); + + let config = config.with_pass(|pass| chain!(pass, after_pass)); let orig = if config.source_maps.enabled() { self.get_orig_src_map(&fm, &config.input_source_map, false)? @@ -997,11 +973,11 @@ impl Compiler { None }; - self.process_js_inner(handler, orig.as_ref(), config) + self.apply_transforms(handler, orig.as_ref(), config) }) } - #[tracing::instrument(level = "info", skip(self, handler, opts))] + #[tracing::instrument(skip(self, handler, opts))] pub fn process_js_file( &self, fm: Arc, @@ -1019,7 +995,7 @@ impl Compiler { ) } - #[tracing::instrument(level = "info", skip_all)] + #[tracing::instrument(skip_all)] pub fn minify( &self, fm: Arc, @@ -1110,10 +1086,10 @@ impl Compiler { jsx: true, decorators: true, decorators_before_export: true, - import_assertions: true, + import_attributes: true, ..Default::default() }), - IsModule::Bool(true), + IsModule::Bool(opts.module), Some(&comments), ) .context("failed to parse input file")?; @@ -1170,15 +1146,19 @@ impl Compiler { Some(&fm.name.to_string()), opts.output_path.clone().map(From::from), opts.inline_sources_content, - target, source_map, &source_map_names, orig.as_ref(), - true, Some(&comments), opts.emit_source_map_columns, - opts.format.ascii_only, &opts.format.preamble, + swc_ecma_codegen::Config::default() + .with_target(target) + .with_minify(true) + .with_ascii_only(opts.format.ascii_only) + .with_emit_assert_for_import_attributes( + opts.format.emit_assert_for_import_attributes, + ), ) }) } @@ -1186,7 +1166,7 @@ impl Compiler { /// You can use custom pass with this method. /// /// There exists a [PassBuilder] to help building custom passes. - #[tracing::instrument(level = "info", skip_all)] + #[tracing::instrument(skip_all)] pub fn process_js( &self, handler: &Handler, @@ -1207,8 +1187,8 @@ impl Compiler { ) } - #[tracing::instrument(level = "info", skip_all)] - fn process_js_inner( + #[tracing::instrument(name = "swc::Compiler::apply_transforms", skip_all)] + fn apply_transforms( &self, handler: &Handler, orig: Option<&sourcemap::SourceMap>, @@ -1245,19 +1225,25 @@ impl Compiler { config.source_file_name.as_deref(), config.output_path, config.inline_sources_content, - config.target, config.source_maps, &source_map_names, orig, - config.minify, config.comments.as_ref().map(|v| v as _), config.emit_source_map_columns, - config - .output - .charset - .map(|v| matches!(v, OutputCharset::Ascii)) - .unwrap_or(false), &config.output.preamble, + swc_ecma_codegen::Config::default() + .with_target(config.target) + .with_minify(config.minify) + .with_ascii_only( + config + .output + .charset + .map(|v| matches!(v, OutputCharset::Ascii)) + .unwrap_or(false), + ) + .with_emit_assert_for_import_attributes( + config.emit_assert_for_import_attributes, + ), ) }) } @@ -1281,7 +1267,7 @@ fn find_swcrc(path: &Path, root: &Path, root_mode: RootMode) -> Option None } -#[tracing::instrument(level = "info", skip_all)] +#[tracing::instrument(skip_all)] fn load_swcrc(path: &Path) -> Result { let content = read_to_string(path).context("failed to read config (.swcrc) file")?; diff --git a/crates/swc/tests/exec.rs b/crates/swc/tests/exec.rs index 6fa8403871..a21cbfe6cd 100644 --- a/crates/swc/tests/exec.rs +++ b/crates/swc/tests/exec.rs @@ -194,7 +194,7 @@ fn create_matrix(entry: &Path) -> Vec { ..Default::default() }, module: if entry.extension().unwrap() == "mjs" { - Some(ModuleConfig::Es6) + Some(ModuleConfig::Es6(Default::default())) } else { Some(ModuleConfig::CommonJs(Default::default())) }, diff --git a/crates/swc/tests/projects.rs b/crates/swc/tests/projects.rs index 2a2594d519..e49497cf37 100644 --- a/crates/swc/tests/projects.rs +++ b/crates/swc/tests/projects.rs @@ -7,8 +7,8 @@ use anyhow::Context; use rayon::prelude::*; use swc::{ config::{ - BuiltInput, Config, FileMatcher, JsMinifyOptions, JscConfig, ModuleConfig, Options, - SourceMapsConfig, TransformConfig, + Config, FileMatcher, JsMinifyOptions, JscConfig, ModuleConfig, Options, SourceMapsConfig, + TransformConfig, }, try_with_handler, BoolOrDataConfig, Compiler, TransformOutput, }; @@ -719,24 +719,7 @@ fn should_visit() { dbg!(config.syntax); - let config = BuiltInput { - program: config.program, - pass: chain!(Panicking, config.pass), - syntax: config.syntax, - target: config.target, - minify: config.minify, - external_helpers: config.external_helpers, - source_maps: config.source_maps, - input_source_map: config.input_source_map, - is_module: config.is_module, - output_path: config.output_path, - source_file_name: config.source_file_name, - preserve_comments: config.preserve_comments, - inline_sources_content: config.inline_sources_content, - comments: config.comments, - emit_source_map_columns: config.emit_source_map_columns, - output: config.output, - }; + let config = config.with_pass(|pass| chain!(Panicking, pass)); if config.minify { let preserve_excl = |_: &BytePos, vc: &mut Vec| -> bool { @@ -760,16 +743,16 @@ fn should_visit() { None, config.output_path, config.inline_sources_content, - config.target, config.source_maps, &Default::default(), None, // TODO: figure out sourcemaps - config.minify, Some(&comments), config.emit_source_map_columns, - false, Default::default(), + swc_ecma_codegen::Config::default() + .with_target(config.target) + .with_minify(config.minify), ) .unwrap() .code) @@ -1108,6 +1091,7 @@ fn issue_7513_2() { fm, handler, &JsMinifyOptions { + module: true, compress: BoolOrDataConfig::from_bool(true), mangle: BoolOrDataConfig::from_obj(MangleOptions { props: None, diff --git a/crates/swc/tests/tsc.rs b/crates/swc/tests/tsc.rs index ecf12920d7..7f2818b953 100644 --- a/crates/swc/tests/tsc.rs +++ b/crates/swc/tests/tsc.rs @@ -315,8 +315,8 @@ fn matrix(input: &Path) -> Vec { Self::Umd => ModuleConfig::Umd(Default::default()), Self::Amd => ModuleConfig::Amd(Default::default()), Self::SystemJs => ModuleConfig::SystemJs(Default::default()), - Self::Es6 => ModuleConfig::Es6, - Self::NodeNext => ModuleConfig::NodeNext, + Self::Es6 => ModuleConfig::Es6(Default::default()), + Self::NodeNext => ModuleConfig::NodeNext(Default::default()), } } } diff --git a/crates/swc_bundler/examples/bundle.rs b/crates/swc_bundler/examples/bundle.rs index 8e1b606f15..8bbd9d6c02 100644 --- a/crates/swc_bundler/examples/bundle.rs +++ b/crates/swc_bundler/examples/bundle.rs @@ -42,10 +42,7 @@ fn print_bundles(cm: Lrc, modules: Vec, minify: bool) { { let wr = JsWriter::new(cm.clone(), "\n", &mut buf, None); let mut emitter = Emitter { - cfg: swc_ecma_codegen::Config { - minify, - ..Default::default() - }, + cfg: swc_ecma_codegen::Config::default().with_minify(true), cm: cm.clone(), comments: None, wr: if minify { diff --git a/crates/swc_bundler/examples/path.rs b/crates/swc_bundler/examples/path.rs index 48ad850d84..7857b6902b 100644 --- a/crates/swc_bundler/examples/path.rs +++ b/crates/swc_bundler/examples/path.rs @@ -46,10 +46,7 @@ fn main() { let wr = stdout(); let mut emitter = Emitter { - cfg: swc_ecma_codegen::Config { - minify: false, - ..Default::default() - }, + cfg: swc_ecma_codegen::Config::default(), cm: cm.clone(), comments: None, wr: Box::new(JsWriter::new(cm, "\n", wr.lock(), None)), diff --git a/crates/swc_bundler/src/bundler/chunk/computed_key.rs b/crates/swc_bundler/src/bundler/chunk/computed_key.rs index 27ea5f3f70..660d1140a3 100644 --- a/crates/swc_bundler/src/bundler/chunk/computed_key.rs +++ b/crates/swc_bundler/src/bundler/chunk/computed_key.rs @@ -84,7 +84,7 @@ where specifiers: vec![specifier], src: None, type_only: false, - asserts: None, + with: None, })), )); } diff --git a/crates/swc_bundler/src/bundler/chunk/merge.rs b/crates/swc_bundler/src/bundler/chunk/merge.rs index d8c08cb833..281725d4b8 100644 --- a/crates/swc_bundler/src/bundler/chunk/merge.rs +++ b/crates/swc_bundler/src/bundler/chunk/merge.rs @@ -768,7 +768,7 @@ where specifiers: vec![specifier], src: None, type_only: false, - asserts: None, + with: None, }, ))); } @@ -816,7 +816,7 @@ where specifiers: vec![specifier], src: None, type_only: false, - asserts: None, + with: None, }, ))); } @@ -883,7 +883,7 @@ where .collect(), src: None, type_only: false, - asserts: None, + with: None, })); extra.push(export); continue; @@ -925,7 +925,7 @@ where specifiers: vec![specifier], src: None, type_only: false, - asserts: None, + with: None, }, ))); } @@ -1140,7 +1140,7 @@ where span: ns.span, specifiers: vec![specifier], src: None, - asserts: None, + with: None, type_only: false, }), )); diff --git a/crates/swc_bundler/src/bundler/import/mod.rs b/crates/swc_bundler/src/bundler/import/mod.rs index 7c3a56c98f..be8ab29a2c 100644 --- a/crates/swc_bundler/src/bundler/import/mod.rs +++ b/crates/swc_bundler/src/bundler/import/mod.rs @@ -250,7 +250,7 @@ where specifiers: vec![], src: Box::new(src.clone()), type_only: false, - asserts: None, + with: None, }; if self.top_level { @@ -657,7 +657,7 @@ where .collect(), src: Box::new(src), type_only: false, - asserts: None, + with: None, }; // if self.top_level { diff --git a/crates/swc_bundler/src/bundler/load.rs b/crates/swc_bundler/src/bundler/load.rs index 19edeb4a91..a50f6d0f2b 100644 --- a/crates/swc_bundler/src/bundler/load.rs +++ b/crates/swc_bundler/src/bundler/load.rs @@ -304,7 +304,7 @@ where specifiers: vec![], src: Box::new(src), type_only: false, - asserts: None, + with: None, }, true, false, diff --git a/crates/swc_bundler/src/debug/mod.rs b/crates/swc_bundler/src/debug/mod.rs index 372fbcd5a6..eca3c6ae12 100644 --- a/crates/swc_bundler/src/debug/mod.rs +++ b/crates/swc_bundler/src/debug/mod.rs @@ -19,10 +19,7 @@ pub(crate) fn print_hygiene(event: &str, cm: &Lrc, t: &Module) { writeln!(w, "==================== @ {} ====================", event).unwrap(); Emitter { - cfg: swc_ecma_codegen::Config { - minify: false, - ..Default::default() - }, + cfg: swc_ecma_codegen::Config::default(), cm: cm.clone(), comments: None, wr: Box::new(JsWriter::new(cm.clone(), "\n", &mut w, None)), diff --git a/crates/swc_bundler/tests/deno.rs b/crates/swc_bundler/tests/deno.rs index 4431ad4eed..ad593a30c7 100644 --- a/crates/swc_bundler/tests/deno.rs +++ b/crates/swc_bundler/tests/deno.rs @@ -1085,10 +1085,7 @@ fn bundle(url: &str, minify: bool) -> String { } Emitter { - cfg: swc_ecma_codegen::Config { - minify, - ..Default::default() - }, + cfg: swc_ecma_codegen::Config::default().with_minify(minify), cm: cm.clone(), comments: None, wr, diff --git a/crates/swc_common/src/input.rs b/crates/swc_common/src/input.rs index 246a98b1d9..b08277c4ca 100644 --- a/crates/swc_common/src/input.rs +++ b/crates/swc_common/src/input.rs @@ -46,7 +46,10 @@ impl<'a> StringInput<'a> { #[inline] pub fn bump_bytes(&mut self, n: usize) { - self.reset_to(self.last_pos + BytePos(n as u32)); + unsafe { + // Safety: We only proceed, not go back. + self.reset_to(self.last_pos + BytePos(n as u32)); + } } } @@ -78,7 +81,7 @@ impl<'a> Input for StringInput<'a> { } #[inline] - fn bump(&mut self) { + unsafe fn bump(&mut self) { if let Some((i, c)) = self.iter.next() { self.last_pos = self.start_pos_of_iter + BytePos((i + c.len_utf8()) as u32); } else { @@ -115,7 +118,7 @@ impl<'a> Input for StringInput<'a> { } #[inline] - fn slice(&mut self, start: BytePos, end: BytePos) -> &str { + unsafe fn slice(&mut self, start: BytePos, end: BytePos) -> &str { debug_assert!(start <= end, "Cannot slice {:?}..{:?}", start, end); let s = self.orig; @@ -184,7 +187,7 @@ impl<'a> Input for StringInput<'a> { } #[inline] - fn reset_to(&mut self, to: BytePos) { + unsafe fn reset_to(&mut self, to: BytePos) { let orig = self.orig; let idx = (to - self.orig_start).0 as usize; @@ -197,12 +200,12 @@ impl<'a> Input for StringInput<'a> { #[inline] fn is_byte(&mut self, c: u8) -> bool { - if self.iter.as_str().is_empty() { - false - } else { - // Safety: We checked that `self.iter.as_str().len() > 0` - unsafe { *self.iter.as_str().as_bytes().get_unchecked(0) == c } - } + self.iter + .as_str() + .as_bytes() + .first() + .map(|b| *b == c) + .unwrap_or(false) } #[inline] @@ -233,7 +236,12 @@ pub trait Input: Clone { fn cur(&mut self) -> Option; fn peek(&mut self) -> Option; fn peek_ahead(&mut self) -> Option; - fn bump(&mut self); + + /// # Safety + /// + /// This should be called only when `cur()` returns `Some`. i.e. + /// when the Input is not empty. + unsafe fn bump(&mut self); /// Returns [None] if it's end of input **or** current character is not an /// ascii character. @@ -253,7 +261,11 @@ pub trait Input: Clone { fn last_pos(&self) -> BytePos; - fn slice(&mut self, start: BytePos, end: BytePos) -> &str; + /// # Safety + /// + /// - start should be less than or equal to end. + /// - start and end should be in the valid range of input. + unsafe fn slice(&mut self, start: BytePos, end: BytePos) -> &str; /// Takes items from stream, testing each one with predicate. returns the /// range of items which passed predicate. @@ -266,7 +278,10 @@ pub trait Input: Clone { where F: FnMut(char) -> bool; - fn reset_to(&mut self, to: BytePos); + /// # Safety + /// + /// - `to` be in the valid range of input. + unsafe fn reset_to(&mut self, to: BytePos); /// Implementors can override the method to make it faster. /// @@ -291,7 +306,10 @@ pub trait Input: Clone { #[inline] fn eat_byte(&mut self, c: u8) -> bool { if self.is_byte(c) { - self.bump(); + unsafe { + // Safety: We are sure that the input is not empty + self.bump(); + } true } else { false @@ -319,13 +337,13 @@ mod tests { #[test] fn src_input_slice_1() { with_test_sess("foo/d", |mut i| { - assert_eq!(i.slice(BytePos(1), BytePos(2)), "f"); + assert_eq!(unsafe { i.slice(BytePos(1), BytePos(2)) }, "f"); assert_eq!(i.last_pos, BytePos(2)); assert_eq!(i.start_pos_of_iter, BytePos(2)); assert_eq!(i.cur(), Some('o')); - assert_eq!(i.slice(BytePos(2), BytePos(4)), "oo"); - assert_eq!(i.slice(BytePos(1), BytePos(4)), "foo"); + assert_eq!(unsafe { i.slice(BytePos(2), BytePos(4)) }, "oo"); + assert_eq!(unsafe { i.slice(BytePos(1), BytePos(4)) }, "foo"); assert_eq!(i.last_pos, BytePos(4)); assert_eq!(i.start_pos_of_iter, BytePos(4)); assert_eq!(i.cur(), Some('/')); @@ -335,11 +353,11 @@ mod tests { #[test] fn src_input_reset_to_1() { with_test_sess("load", |mut i| { - assert_eq!(i.slice(BytePos(1), BytePos(3)), "lo"); + assert_eq!(unsafe { i.slice(BytePos(1), BytePos(3)) }, "lo"); assert_eq!(i.last_pos, BytePos(3)); assert_eq!(i.start_pos_of_iter, BytePos(3)); assert_eq!(i.cur(), Some('a')); - i.reset_to(BytePos(1)); + unsafe { i.reset_to(BytePos(1)) }; assert_eq!(i.cur(), Some('l')); assert_eq!(i.last_pos, BytePos(1)); @@ -360,11 +378,15 @@ mod tests { assert_eq!(i.start_pos_of_iter, BytePos(4)); assert_eq!(i.cur(), Some('/')); - i.bump(); + unsafe { + i.bump(); + } assert_eq!(i.last_pos, BytePos(5)); assert_eq!(i.cur(), Some('d')); - i.bump(); + unsafe { + i.bump(); + } assert_eq!(i.last_pos, BytePos(6)); assert_eq!(i.cur(), None); }); diff --git a/crates/swc_common/src/source_map.rs b/crates/swc_common/src/source_map.rs index 1b8c97f8aa..cc121bee11 100644 --- a/crates/swc_common/src/source_map.rs +++ b/crates/swc_common/src/source_map.rs @@ -1266,6 +1266,9 @@ impl SourceMap { Some(ref f) if f.start_pos <= pos && pos < f.end_pos => f, _ => { f = self.lookup_source_file(pos); + if config.skip(&f.name) { + continue; + } src_id = builder.add_source(&config.file_name_to_source(&f.name)); inline_sources_content = config.inline_sources_content(&f.name); @@ -1447,8 +1450,9 @@ pub trait SourceMapGenConfig { true } - fn skip(&self, _f: &FileName) -> bool { - false + /// By default, we skip internal files. + fn skip(&self, f: &FileName) -> bool { + matches!(f, FileName::Internal(..)) } } diff --git a/crates/swc_css_ast/src/at_rule.rs b/crates/swc_css_ast/src/at_rule.rs index 612454ccab..460ad0b509 100644 --- a/crates/swc_css_ast/src/at_rule.rs +++ b/crates/swc_css_ast/src/at_rule.rs @@ -4,8 +4,9 @@ use swc_atoms::{Atom, JsWord}; use swc_common::{ast_node, util::take::Take, EqIgnoreSpan, Span}; use crate::{ - CustomIdent, CustomPropertyName, DashedIdent, Declaration, Dimension, FamilyName, Function, - Ident, ListOfComponentValues, Number, Percentage, Ratio, SelectorList, SimpleBlock, Str, Url, + CustomIdent, CustomPropertyName, DashedIdent, Declaration, Dimension, FamilyName, + ForgivingSelectorList, Function, Ident, ListOfComponentValues, Number, Percentage, Ratio, + SelectorList, SimpleBlock, Str, Url, }; #[ast_node("AtRule")] @@ -84,6 +85,18 @@ pub enum AtRulePrelude { ContainerPrelude(ContainerCondition), #[tag("CustomMedia")] CustomMediaPrelude(CustomMediaQuery), + #[tag("ScopeRange")] + ScopePrelude(ScopeRange), +} + +#[ast_node("ScopeRange")] +#[derive(Eq, Hash, EqIgnoreSpan)] +pub struct ScopeRange { + pub span: Span, + /// https://drafts.csswg.org/css-cascade-6/#typedef-scope-start + pub scope_start: Option, + /// https://drafts.csswg.org/css-cascade-6/#typedef-scope-end + pub scope_end: Option, } #[ast_node] diff --git a/crates/swc_css_codegen/src/lib.rs b/crates/swc_css_codegen/src/lib.rs index f3ff1339ca..dd4bf349ae 100644 --- a/crates/swc_css_codegen/src/lib.rs +++ b/crates/swc_css_codegen/src/lib.rs @@ -324,6 +324,9 @@ where n ) } + AtRulePrelude::ScopePrelude(n) => { + emit!(self, n); + } } } @@ -2459,6 +2462,23 @@ where } } + #[emitter] + fn emit_scope_range(&mut self, n: &ScopeRange) -> Result { + if let Some(start) = &n.scope_start { + formatting_space!(self); + write_raw!(self, "("); + emit!(self, start); + write_raw!(self, ")"); + } + if let Some(end) = &n.scope_end { + write_raw!(self, " to"); + space!(self); + write_raw!(self, "("); + emit!(self, end); + write_raw!(self, ")"); + } + } + fn emit_list_pseudo_element_selector_children( &mut self, nodes: &[PseudoElementSelectorChildren], diff --git a/crates/swc_css_modules/src/lib.rs b/crates/swc_css_modules/src/lib.rs index 81f4da7056..49678021c4 100644 --- a/crates/swc_css_modules/src/lib.rs +++ b/crates/swc_css_modules/src/lib.rs @@ -204,13 +204,13 @@ where n.visit_mut_children_with(self); if let QualifiedRulePrelude::SelectorList(sel) = &n.prelude { - // - if sel.children.len() == 1 && sel.children[0].children.len() == 1 { - if let ComplexSelectorChildren::CompoundSelector(sel) = &sel.children[0].children[0] - { - if sel.subclass_selectors.len() == 1 { - if let SubclassSelector::Class(class_sel) = &sel.subclass_selectors[0] { - if let Some(composes) = self.data.composes_for_current.take() { + let composes = self.data.composes_for_current.take(); + + for child in &sel.children { + if let ComplexSelectorChildren::CompoundSelector(sel) = &child.children[0] { + for subclass_sel in &sel.subclass_selectors { + if let SubclassSelector::Class(class_sel) = &subclass_sel { + if let Some(composes) = &composes { let key = self .data .renamed_to_orig @@ -218,7 +218,27 @@ where .cloned(); if let Some(key) = key { - self.result.renamed.entry(key).or_default().extend(composes); + let mut renamed = self.result.renamed.clone(); + let class_names = self.result.renamed.entry(key).or_default(); + + class_names.extend(composes.clone()); + + for composed_class_name in composes.iter() { + if let CssClassName::Local { name } = composed_class_name { + if let Some(original_class_name) = + self.data.renamed_to_orig.get(&name.value) + { + class_names.extend( + renamed + .entry(original_class_name.clone()) + .or_default() + .split_at(1) + .1 + .to_vec(), + ); + } + } + } } } } diff --git a/crates/swc_css_parser/src/error.rs b/crates/swc_css_parser/src/error.rs index 56b0857860..6ebdf85e4c 100644 --- a/crates/swc_css_parser/src/error.rs +++ b/crates/swc_css_parser/src/error.rs @@ -71,6 +71,7 @@ impl Error { ErrorKind::InvalidKeyframesName(s) => { format!("{} is not valid name for keyframes", s).into() } + ErrorKind::InvalidScopeAtRule => "Invalid @scope at-rule".into(), } } @@ -115,6 +116,7 @@ pub enum ErrorKind { InvalidAnPlusBMicrosyntax, InvalidCustomIdent(JsWord), InvalidKeyframesName(&'static str), + InvalidScopeAtRule, UnknownAtRuleNotTerminated, } diff --git a/crates/swc_css_parser/src/lexer/mod.rs b/crates/swc_css_parser/src/lexer/mod.rs index 3fb292d6f4..ad05725ceb 100644 --- a/crates/swc_css_parser/src/lexer/mod.rs +++ b/crates/swc_css_parser/src/lexer/mod.rs @@ -139,7 +139,10 @@ where } fn reset(&mut self, state: &Self::State) { - self.input.reset_to(state.pos); + unsafe { + // Safety: state.pos is created from a valid position. + self.input.reset_to(state.pos); + } } fn take_errors(&mut self) -> Vec { @@ -199,7 +202,10 @@ where self.cur_pos = self.input.last_pos(); if cur.is_some() { - self.input.bump(); + unsafe { + // Safety: cur is Some + self.input.bump(); + } } cur @@ -207,7 +213,11 @@ where #[inline(always)] fn reconsume(&mut self) { - self.input.reset_to(self.cur_pos); + unsafe { + // Safety: self.cur_pos is a position generated by self.input, meaning it is + // valid. + self.input.reset_to(self.cur_pos); + } } #[cold] diff --git a/crates/swc_css_parser/src/macros.rs b/crates/swc_css_parser/src/macros.rs index 8aca0cdfe9..4a8f21bb09 100644 --- a/crates/swc_css_parser/src/macros.rs +++ b/crates/swc_css_parser/src/macros.rs @@ -146,4 +146,8 @@ macro_rules! tok { (">") => { swc_css_ast::Token::Delim { value: '>' } }; + + ("to") => { + swc_css_ast::Token::Ident { value: js_word!("to"), .. } + }; } diff --git a/crates/swc_css_parser/src/parser/at_rules/mod.rs b/crates/swc_css_parser/src/parser/at_rules/mod.rs index 46f25c7a8e..fee6245cc5 100644 --- a/crates/swc_css_parser/src/parser/at_rules/mod.rs +++ b/crates/swc_css_parser/src/parser/at_rules/mod.rs @@ -424,6 +424,15 @@ where None } + js_word!("scope") => { + self.input.skip_ws(); + + let prelude = AtRulePrelude::ScopePrelude(self.parse()?); + + self.input.skip_ws(); + + Some(prelude) + } _ => { return Err(Error::new(Default::default(), ErrorKind::Ignore)); } @@ -756,6 +765,13 @@ where rule_list } + js_word!("scope") => { + let rule_list = self.parse_as::>()?; + let rule_list: Vec = + rule_list.into_iter().map(ComponentValue::from).collect(); + + rule_list + } _ => { return Err(Error::new(Default::default(), ErrorKind::Ignore)); } @@ -2625,3 +2641,67 @@ where }) } } + +impl Parse for Parser +where + I: ParserInput, +{ + fn parse(&mut self) -> PResult { + let span = self.input.cur_span(); + + if is!(self, EOF) { + return Ok(ScopeRange { + span: span!(self, span.lo), + scope_start: None, + scope_end: None, + }); + } + + match cur!(self) { + tok!("(") => { + bump!(self); + let start = self.parse()?; + expect!(self, ")"); + self.input.skip_ws(); + + let end = if is!(self, EOF) { + None + } else if is_case_insensitive_ident!(self, "to") { + bump!(self); + self.input.skip_ws(); + expect!(self, "("); + let result = self.parse()?; + expect!(self, ")"); + Some(result) + } else { + None + }; + + Ok(ScopeRange { + span: span!(self, span.lo), + scope_start: Some(start), + scope_end: end, + }) + } + _ => { + if is_case_insensitive_ident!(self, "to") { + bump!(self); + + self.input.skip_ws(); + + expect!(self, "("); + let end = self.parse()?; + expect!(self, ")"); + + return Ok(ScopeRange { + span: span!(self, span.lo), + scope_start: None, + scope_end: Some(end), + }); + } + + return Err(Error::new(span, ErrorKind::InvalidScopeAtRule)); + } + } + } +} diff --git a/crates/swc_css_visit/src/lib.rs b/crates/swc_css_visit/src/lib.rs index 672907ad44..e0bbf0ce63 100644 --- a/crates/swc_css_visit/src/lib.rs +++ b/crates/swc_css_visit/src/lib.rs @@ -641,6 +641,13 @@ define!({ LayerPrelude(LayerPrelude), ContainerPrelude(ContainerCondition), CustomMediaPrelude(CustomMediaQuery), + ScopePrelude(ScopeRange), + } + + pub struct ScopeRange { + pub span: Span, + pub scope_start: Option, + pub scope_end: Option, } pub struct ListOfComponentValues { diff --git a/crates/swc_ecma_ast/src/module_decl.rs b/crates/swc_ecma_ast/src/module_decl.rs index 2de8963636..368299b140 100644 --- a/crates/swc_ecma_ast/src/module_decl.rs +++ b/crates/swc_ecma_ast/src/module_decl.rs @@ -84,7 +84,7 @@ pub struct ImportDecl { pub type_only: bool, #[cfg_attr(feature = "serde-impl", serde(default))] - pub asserts: Option>, + pub with: Option>, } impl Take for ImportDecl { @@ -94,7 +94,7 @@ impl Take for ImportDecl { specifiers: Take::dummy(), src: Take::dummy(), type_only: Default::default(), - asserts: Take::dummy(), + with: Take::dummy(), } } } @@ -113,7 +113,7 @@ pub struct ExportAll { pub type_only: bool, #[cfg_attr(feature = "serde-impl", serde(default))] - pub asserts: Option>, + pub with: Option>, } impl Take for ExportAll { @@ -122,7 +122,7 @@ impl Take for ExportAll { span: DUMMY_SP, src: Take::dummy(), type_only: Default::default(), - asserts: Take::dummy(), + with: Take::dummy(), } } } @@ -144,7 +144,7 @@ pub struct NamedExport { pub type_only: bool, #[cfg_attr(feature = "serde-impl", serde(default))] - pub asserts: Option>, + pub with: Option>, } impl Take for NamedExport { @@ -154,7 +154,7 @@ impl Take for NamedExport { specifiers: Take::dummy(), src: Take::dummy(), type_only: Default::default(), - asserts: Take::dummy(), + with: Take::dummy(), } } } diff --git a/crates/swc_ecma_codegen/src/config.rs b/crates/swc_ecma_codegen/src/config.rs index 204d77a041..90ff3f84d4 100644 --- a/crates/swc_ecma_codegen/src/config.rs +++ b/crates/swc_ecma_codegen/src/config.rs @@ -5,6 +5,7 @@ use swc_ecma_ast::EsVersion; #[derive(Debug, Clone, Copy)] #[cfg_attr(feature = "serde-impl", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde-impl", serde(rename_all = "camelCase"))] +#[non_exhaustive] pub struct Config { /// The target runtime environment. /// @@ -32,6 +33,9 @@ pub struct Config { /// Defaults to `false`. #[cfg_attr(feature = "serde-impl", serde(default))] pub omit_last_semi: bool, + + #[cfg_attr(feature = "serde-impl", serde(default))] + pub emit_assert_for_import_attributes: bool, } impl Default for Config { @@ -41,6 +45,37 @@ impl Default for Config { minify: false, ascii_only: false, omit_last_semi: false, + emit_assert_for_import_attributes: false, } } } + +impl Config { + pub fn with_target(mut self, target: EsVersion) -> Self { + self.target = target; + self + } + + pub fn with_minify(mut self, minify: bool) -> Self { + self.minify = minify; + self + } + + pub fn with_ascii_only(mut self, ascii_only: bool) -> Self { + self.ascii_only = ascii_only; + self + } + + pub fn with_omit_last_semi(mut self, omit_last_semi: bool) -> Self { + self.omit_last_semi = omit_last_semi; + self + } + + pub fn with_emit_assert_for_import_attributes( + mut self, + emit_assert_for_import_attributes: bool, + ) -> Self { + self.emit_assert_for_import_attributes = emit_assert_for_import_attributes; + self + } +} diff --git a/crates/swc_ecma_codegen/src/lib.rs b/crates/swc_ecma_codegen/src/lib.rs index 72a7dc387a..a1522134f8 100644 --- a/crates/swc_ecma_codegen/src/lib.rs +++ b/crates/swc_ecma_codegen/src/lib.rs @@ -308,11 +308,15 @@ where emit!(n.src); - if let Some(asserts) = &n.asserts { + if let Some(with) = &n.with { formatting_space!(); - keyword!("assert"); + if self.cfg.emit_assert_for_import_attributes { + keyword!("assert"); + } else { + keyword!("with") + }; formatting_space!(); - emit!(asserts); + emit!(with); } semi!(); @@ -451,11 +455,15 @@ where formatting_space!(); emit!(src); - if let Some(asserts) = &node.asserts { + if let Some(with) = &node.with { formatting_space!(); - keyword!("assert"); + if self.cfg.emit_assert_for_import_attributes { + keyword!("assert"); + } else { + keyword!("with") + }; formatting_space!(); - emit!(asserts); + emit!(with); } } semi!(); @@ -477,11 +485,15 @@ where formatting_space!(); emit!(node.src); - if let Some(asserts) = &node.asserts { + if let Some(with) = &node.with { formatting_space!(); - keyword!("assert"); + if self.cfg.emit_assert_for_import_attributes { + keyword!("assert"); + } else { + keyword!("with") + }; formatting_space!(); - emit!(asserts); + emit!(with); } semi!(); @@ -1879,12 +1891,23 @@ where } if let Some(ref arg) = node.arg { - if !node.delegate && arg.starts_with_alpha_num() { + let need_paren = node + .arg + .as_deref() + .map(|expr| self.has_leading_comment(expr)) + .unwrap_or(false); + if need_paren { + punct!("(") + } else if !node.delegate && arg.starts_with_alpha_num() { space!() } else { formatting_space!() } + emit!(node.arg); + if need_paren { + punct!(")") + } } } @@ -2743,6 +2766,8 @@ where #[emitter] #[tracing::instrument(skip_all)] fn emit_expr_stmt(&mut self, e: &ExprStmt) -> Result { + self.emit_leading_comments_of_span(e.span, false)?; + emit!(e.expr); semi!(); diff --git a/crates/swc_ecma_codegen/tests/fixture.rs b/crates/swc_ecma_codegen/tests/fixture.rs index d162af295e..aba025d528 100644 --- a/crates/swc_ecma_codegen/tests/fixture.rs +++ b/crates/swc_ecma_codegen/tests/fixture.rs @@ -49,10 +49,7 @@ fn run(input: &Path, minify: bool) { } let mut emitter = Emitter { - cfg: swc_ecma_codegen::Config { - minify, - ..Default::default() - }, + cfg: swc_ecma_codegen::Config::default().with_minify(minify), cm, comments: None, wr, diff --git a/crates/swc_ecma_codegen/tests/sourcemap.rs b/crates/swc_ecma_codegen/tests/sourcemap.rs index b8d2e7af2d..dd8358b06d 100644 --- a/crates/swc_ecma_codegen/tests/sourcemap.rs +++ b/crates/swc_ecma_codegen/tests/sourcemap.rs @@ -322,12 +322,10 @@ fn identity(entry: PathBuf) { wr = Box::new(swc_ecma_codegen::text_writer::omit_trailing_semi(wr)); let mut emitter = Emitter { - cfg: swc_ecma_codegen::Config { - minify: true, - target: EsVersion::Es5, - ascii_only: true, - ..Default::default() - }, + cfg: swc_ecma_codegen::Config::default() + .with_minify(true) + .with_ascii_only(true) + .with_target(EsVersion::Es5), cm: cm.clone(), wr, comments: None, diff --git a/crates/swc_ecma_codegen/tests/test262.rs b/crates/swc_ecma_codegen/tests/test262.rs index 8e0b270283..b8e60b4e4a 100644 --- a/crates/swc_ecma_codegen/tests/test262.rs +++ b/crates/swc_ecma_codegen/tests/test262.rs @@ -136,11 +136,9 @@ fn do_test(entry: &Path, minify: bool) { } let mut emitter = Emitter { - cfg: swc_ecma_codegen::Config { - minify, - target: EsVersion::Es5, - ..Default::default() - }, + cfg: swc_ecma_codegen::Config::default() + .with_minify(minify) + .with_target(EsVersion::Es5), cm, wr, comments: if minify { None } else { Some(&comments) }, diff --git a/crates/swc_ecma_dep_graph/src/lib.rs b/crates/swc_ecma_dep_graph/src/lib.rs index db84d697fa..e94780e9a5 100644 --- a/crates/swc_ecma_dep_graph/src/lib.rs +++ b/crates/swc_ecma_dep_graph/src/lib.rs @@ -41,7 +41,7 @@ pub enum ImportAssertion { } #[derive(Clone, Debug, Eq, PartialEq)] -pub enum ImportAssertions { +pub enum ImportAttributes { /// There was no import assertions object literal. None, /// The set of assertion keys could not be statically analyzed. @@ -51,16 +51,16 @@ pub enum ImportAssertions { Known(HashMap), } -impl Default for ImportAssertions { +impl Default for ImportAttributes { fn default() -> Self { - ImportAssertions::None + ImportAttributes::None } } -impl ImportAssertions { +impl ImportAttributes { pub fn get(&self, key: &str) -> Option<&String> { match self { - ImportAssertions::Known(map) => match map.get(key) { + ImportAttributes::Known(map) => match map.get(key) { Some(ImportAssertion::Known(value)) => Some(value), _ => None, }, @@ -84,7 +84,7 @@ pub struct DependencyDescriptor { /// The span of the specifier. pub specifier_span: Span, /// Import assertions for this dependency. - pub import_assertions: ImportAssertions, + pub import_attributes: ImportAttributes, } struct DependencyCollector<'a> { @@ -110,7 +110,7 @@ impl<'a> Visit for DependencyCollector<'a> { } else { DependencyKind::Import }; - let import_assertions = parse_import_assertions(node.asserts.as_deref()); + let import_attributes = parse_import_attributes(node.with.as_deref()); self.items.push(DependencyDescriptor { kind, is_dynamic: false, @@ -118,7 +118,7 @@ impl<'a> Visit for DependencyCollector<'a> { span: node.span, specifier, specifier_span: node.src.span, - import_assertions, + import_attributes, }); } @@ -131,7 +131,7 @@ impl<'a> Visit for DependencyCollector<'a> { } else { DependencyKind::Export }; - let import_assertions = parse_import_assertions(node.asserts.as_deref()); + let import_attributes = parse_import_attributes(node.with.as_deref()); self.items.push(DependencyDescriptor { kind, is_dynamic: false, @@ -139,7 +139,7 @@ impl<'a> Visit for DependencyCollector<'a> { span: node.span, specifier, specifier_span: src.span, - import_assertions, + import_attributes, }); } } @@ -152,7 +152,7 @@ impl<'a> Visit for DependencyCollector<'a> { } else { DependencyKind::Export }; - let import_assertions = parse_import_assertions(node.asserts.as_deref()); + let import_attributes = parse_import_attributes(node.with.as_deref()); self.items.push(DependencyDescriptor { kind, is_dynamic: false, @@ -160,7 +160,7 @@ impl<'a> Visit for DependencyCollector<'a> { span: node.span, specifier, specifier_span: node.src.span, - import_assertions, + import_attributes, }); } @@ -175,8 +175,9 @@ impl<'a> Visit for DependencyCollector<'a> { span: node.span, specifier, specifier_span: node.arg.span, - import_assertions: Default::default(), + import_attributes: Default::default(), }); + node.visit_children_with(self); } fn visit_module_items(&mut self, items: &[ast::ModuleItem]) { @@ -230,7 +231,7 @@ impl<'a> Visit for DependencyCollector<'a> { span: node.span, specifier, specifier_span: str_.span, - import_assertions: dynamic_import_assertions, + import_attributes: dynamic_import_assertions, }); } } @@ -259,7 +260,7 @@ impl<'a> Visit for DependencyCollector<'a> { span: node.span, specifier, specifier_span: expr.span, - import_assertions: Default::default(), + import_attributes: Default::default(), }); } } @@ -268,13 +269,13 @@ impl<'a> Visit for DependencyCollector<'a> { /// Parses import assertions into a hashmap. According to proposal the values /// can only be strings (https://github.com/tc39/proposal-import-assertions#should-more-than-just-strings-be-supported-as-attribute-values) /// and thus non-string values are skipped. -fn parse_import_assertions(asserts: Option<&ast::ObjectLit>) -> ImportAssertions { - let asserts = match asserts { - Some(asserts) => asserts, - None => return ImportAssertions::None, +fn parse_import_attributes(attrs: Option<&ast::ObjectLit>) -> ImportAttributes { + let attrs = match attrs { + Some(with) => with, + None => return ImportAttributes::None, }; let mut import_assertions = HashMap::new(); - for prop in asserts.props.iter() { + for prop in attrs.props.iter() { if let ast::PropOrSpread::Prop(prop) = prop { if let ast::Prop::KeyValue(key_value) = &**prop { let maybe_key = match &key_value.key { @@ -292,23 +293,23 @@ fn parse_import_assertions(asserts: Option<&ast::ObjectLit>) -> ImportAssertions } } } - ImportAssertions::Known(import_assertions) + ImportAttributes::Known(import_assertions) } /// Parses import assertions from the second arg of a dynamic import. -fn parse_dynamic_import_assertions(arg: Option<&ast::ExprOrSpread>) -> ImportAssertions { +fn parse_dynamic_import_assertions(arg: Option<&ast::ExprOrSpread>) -> ImportAttributes { let arg = match arg { Some(arg) => arg, - None => return ImportAssertions::None, + None => return ImportAttributes::None, }; if arg.spread.is_some() { - return ImportAssertions::Unknown; + return ImportAttributes::Unknown; } let object_lit = match &*arg.expr { ast::Expr::Object(object_lit) => object_lit, - _ => return ImportAssertions::Unknown, + _ => return ImportAttributes::Unknown, }; let mut assertions_map = HashMap::new(); @@ -317,37 +318,37 @@ fn parse_dynamic_import_assertions(arg: Option<&ast::ExprOrSpread>) -> ImportAss for prop in object_lit.props.iter() { let prop = match prop { ast::PropOrSpread::Prop(prop) => prop, - _ => return ImportAssertions::Unknown, + _ => return ImportAttributes::Unknown, }; let key_value = match &**prop { ast::Prop::KeyValue(key_value) => key_value, - _ => return ImportAssertions::Unknown, + _ => return ImportAttributes::Unknown, }; let key = match &key_value.key { ast::PropName::Str(key) => key.value.to_string(), ast::PropName::Ident(ident) => ident.sym.to_string(), - _ => return ImportAssertions::Unknown, + _ => return ImportAttributes::Unknown, }; - if key == "assert" { + if key == "assert" || key == "with" { had_assert_key = true; let assertions_lit = match &*key_value.value { ast::Expr::Object(assertions_lit) => assertions_lit, - _ => return ImportAssertions::Unknown, + _ => return ImportAttributes::Unknown, }; for prop in assertions_lit.props.iter() { let prop = match prop { ast::PropOrSpread::Prop(prop) => prop, - _ => return ImportAssertions::Unknown, + _ => return ImportAttributes::Unknown, }; let key_value = match &**prop { ast::Prop::KeyValue(key_value) => key_value, - _ => return ImportAssertions::Unknown, + _ => return ImportAttributes::Unknown, }; let key = match &key_value.key { ast::PropName::Str(key) => key.value.to_string(), ast::PropName::Ident(ident) => ident.sym.to_string(), - _ => return ImportAssertions::Unknown, + _ => return ImportAttributes::Unknown, }; if let ast::Expr::Lit(value_lit) = &*key_value.value { assertions_map.insert( @@ -366,9 +367,9 @@ fn parse_dynamic_import_assertions(arg: Option<&ast::ExprOrSpread>) -> ImportAss } if had_assert_key { - ImportAssertions::Known(assertions_map) + ImportAttributes::Known(assertions_map) } else { - ImportAssertions::None + ImportAttributes::None } } @@ -477,7 +478,7 @@ try { span: Span::new(BytePos(1), BytePos(34), Default::default()), specifier: JsWord::from("./test.ts"), specifier_span: Span::new(BytePos(22), BytePos(33), Default::default()), - import_assertions: Default::default(), + import_attributes: Default::default(), }, DependencyDescriptor { kind: DependencyKind::ImportType, @@ -490,7 +491,7 @@ try { span: Span::new(BytePos(48), BytePos(86), Default::default()), specifier: JsWord::from("./foo.d.ts"), specifier_span: Span::new(BytePos(73), BytePos(85), Default::default()), - import_assertions: Default::default(), + import_attributes: Default::default(), }, DependencyDescriptor { kind: DependencyKind::Export, @@ -503,7 +504,7 @@ try { span: Span::new(BytePos(115), BytePos(149), Default::default()), specifier: JsWord::from("./buzz.ts"), specifier_span: Span::new(BytePos(137), BytePos(148), Default::default()), - import_assertions: Default::default(), + import_attributes: Default::default(), }, DependencyDescriptor { kind: DependencyKind::ExportType, @@ -523,7 +524,7 @@ try { span: Span::new(BytePos(181), BytePos(221), Default::default()), specifier: JsWord::from("./fizz.d.ts"), specifier_span: Span::new(BytePos(207), BytePos(220), Default::default()), - import_assertions: Default::default(), + import_attributes: Default::default(), }, DependencyDescriptor { kind: DependencyKind::Require, @@ -532,7 +533,7 @@ try { span: Span::new(BytePos(239), BytePos(254), Default::default()), specifier: JsWord::from("path"), specifier_span: Span::new(BytePos(247), BytePos(253), Default::default()), - import_assertions: Default::default(), + import_attributes: Default::default(), }, DependencyDescriptor { kind: DependencyKind::Import, @@ -541,7 +542,7 @@ try { span: Span::new(BytePos(274), BytePos(293), Default::default()), specifier: JsWord::from("./foo1.ts"), specifier_span: Span::new(BytePos(281), BytePos(292), Default::default()), - import_assertions: Default::default(), + import_attributes: Default::default(), }, DependencyDescriptor { kind: DependencyKind::Import, @@ -550,7 +551,7 @@ try { span: Span::new(BytePos(324), BytePos(342), Default::default()), specifier: JsWord::from("./foo.ts"), specifier_span: Span::new(BytePos(331), BytePos(341), Default::default()), - import_assertions: Default::default(), + import_attributes: Default::default(), }, DependencyDescriptor { kind: DependencyKind::Require, @@ -559,7 +560,7 @@ try { span: Span::new(BytePos(395), BytePos(418), Default::default()), specifier: JsWord::from("some_package"), specifier_span: Span::new(BytePos(403), BytePos(417), Default::default()), - import_assertions: Default::default(), + import_attributes: Default::default(), }, DependencyDescriptor { kind: DependencyKind::ImportEquals, @@ -568,7 +569,7 @@ try { span: Span::new(BytePos(449), BytePos(491), Default::default()), specifier: JsWord::from("some_package_foo"), specifier_span: Span::new(BytePos(471), BytePos(489), Default::default()), - import_assertions: Default::default(), + import_attributes: Default::default(), }, DependencyDescriptor { kind: DependencyKind::ImportType, @@ -577,7 +578,7 @@ try { span: Span::new(BytePos(492), BytePos(547), Default::default()), specifier: JsWord::from("some_package_foo_type"), specifier_span: Span::new(BytePos(522), BytePos(545), Default::default()), - import_assertions: Default::default(), + import_attributes: Default::default(), }, DependencyDescriptor { kind: DependencyKind::ExportEquals, @@ -586,7 +587,7 @@ try { span: Span::new(BytePos(548), BytePos(597), Default::default()), specifier: JsWord::from("some_package_bar"), specifier_span: Span::new(BytePos(577), BytePos(595), Default::default()), - import_assertions: Default::default(), + import_attributes: Default::default(), }, DependencyDescriptor { kind: DependencyKind::Require, @@ -595,7 +596,7 @@ try { span: Span::new(BytePos(612), BytePos(651), Default::default()), specifier: JsWord::from("some_package_resolve"), specifier_span: Span::new(BytePos(628), BytePos(650), Default::default()), - import_assertions: Default::default(), + import_attributes: Default::default(), }, DependencyDescriptor { kind: DependencyKind::Require, @@ -604,7 +605,7 @@ try { span: Span::new(BytePos(676), BytePos(719), Default::default()), specifier: JsWord::from("some_package_resolve_foo"), specifier_span: Span::new(BytePos(692), BytePos(718), Default::default()), - import_assertions: Default::default(), + import_attributes: Default::default(), }, ] ); @@ -630,7 +631,7 @@ const d9 = await import("./d9.json", { assert: { type: "json", ...bar } }); const d10 = await import("./d10.json", { assert: { type: "json", ["type"]: "bad" } }); "#; let (module, comments) = helper("test.ts", source).unwrap(); - let expected_assertions1 = ImportAssertions::Known({ + let expected_assertions1 = ImportAttributes::Known({ let mut map = HashMap::new(); map.insert( "type".to_string(), @@ -638,7 +639,7 @@ const d10 = await import("./d10.json", { assert: { type: "json", ["type"]: "bad" ); map }); - let expected_assertions2 = ImportAssertions::Known({ + let expected_assertions2 = ImportAttributes::Known({ let mut map = HashMap::new(); map.insert( "type".to_string(), @@ -646,7 +647,7 @@ const d10 = await import("./d10.json", { assert: { type: "json", ["type"]: "bad" ); map }); - let dynamic_expected_assertions2 = ImportAssertions::Known({ + let dynamic_expected_assertions2 = ImportAttributes::Known({ let mut map = HashMap::new(); map.insert( "type".to_string(), @@ -666,7 +667,7 @@ const d10 = await import("./d10.json", { assert: { type: "json", ["type"]: "bad" span: Span::new(BytePos(1), BytePos(66), Default::default()), specifier: JsWord::from("./test.ts"), specifier_span: Span::new(BytePos(22), BytePos(33), Default::default()), - import_assertions: expected_assertions1.clone(), + import_attributes: expected_assertions1.clone(), }, DependencyDescriptor { kind: DependencyKind::Export, @@ -675,7 +676,7 @@ const d10 = await import("./d10.json", { assert: { type: "json", ["type"]: "bad" span: Span::new(BytePos(67), BytePos(125), Default::default()), specifier: JsWord::from("./test.ts"), specifier_span: Span::new(BytePos(81), BytePos(92), Default::default()), - import_assertions: expected_assertions1, + import_attributes: expected_assertions1, }, DependencyDescriptor { kind: DependencyKind::Export, @@ -684,7 +685,7 @@ const d10 = await import("./d10.json", { assert: { type: "json", ["type"]: "bad" span: Span::new(BytePos(126), BytePos(186), Default::default()), specifier: JsWord::from("./test.json"), specifier_span: Span::new(BytePos(146), BytePos(159), Default::default()), - import_assertions: expected_assertions2.clone(), + import_attributes: expected_assertions2.clone(), }, DependencyDescriptor { kind: DependencyKind::Import, @@ -693,7 +694,7 @@ const d10 = await import("./d10.json", { assert: { type: "json", ["type"]: "bad" span: Span::new(BytePos(187), BytePos(240), Default::default()), specifier: JsWord::from("./foo.json"), specifier_span: Span::new(BytePos(203), BytePos(215), Default::default()), - import_assertions: expected_assertions2, + import_attributes: expected_assertions2, }, DependencyDescriptor { kind: DependencyKind::Import, @@ -702,7 +703,7 @@ const d10 = await import("./d10.json", { assert: { type: "json", ["type"]: "bad" span: Span::new(BytePos(260), BytePos(313), Default::default()), specifier: JsWord::from("./fizz.json"), specifier_span: Span::new(BytePos(267), BytePos(280), Default::default()), - import_assertions: dynamic_expected_assertions2.clone(), + import_attributes: dynamic_expected_assertions2.clone(), }, DependencyDescriptor { kind: DependencyKind::Import, @@ -711,7 +712,7 @@ const d10 = await import("./d10.json", { assert: { type: "json", ["type"]: "bad" span: Span::new(BytePos(334), BytePos(387), Default::default()), specifier: JsWord::from("./buzz.json"), specifier_span: Span::new(BytePos(341), BytePos(354), Default::default()), - import_assertions: dynamic_expected_assertions2, + import_attributes: dynamic_expected_assertions2, }, DependencyDescriptor { kind: DependencyKind::Import, @@ -720,7 +721,7 @@ const d10 = await import("./d10.json", { assert: { type: "json", ["type"]: "bad" span: Span::new(BytePos(406), BytePos(425), Default::default()), specifier: JsWord::from("./d1.json"), specifier_span: Span::new(BytePos(413), BytePos(424), Default::default()), - import_assertions: Default::default(), + import_attributes: Default::default(), }, DependencyDescriptor { kind: DependencyKind::Import, @@ -729,7 +730,7 @@ const d10 = await import("./d10.json", { assert: { type: "json", ["type"]: "bad" span: Span::new(BytePos(444), BytePos(467), Default::default()), specifier: JsWord::from("./d2.json"), specifier_span: Span::new(BytePos(451), BytePos(462), Default::default()), - import_assertions: Default::default(), + import_attributes: Default::default(), }, DependencyDescriptor { kind: DependencyKind::Import, @@ -738,7 +739,7 @@ const d10 = await import("./d10.json", { assert: { type: "json", ["type"]: "bad" span: Span::new(BytePos(486), BytePos(510), Default::default()), specifier: JsWord::from("./d3.json"), specifier_span: Span::new(BytePos(493), BytePos(504), Default::default()), - import_assertions: ImportAssertions::Unknown, + import_attributes: ImportAttributes::Unknown, }, DependencyDescriptor { kind: DependencyKind::Import, @@ -747,7 +748,7 @@ const d10 = await import("./d10.json", { assert: { type: "json", ["type"]: "bad" span: Span::new(BytePos(529), BytePos(564), Default::default()), specifier: JsWord::from("./d4.json"), specifier_span: Span::new(BytePos(536), BytePos(547), Default::default()), - import_assertions: ImportAssertions::Known(HashMap::new()), + import_attributes: ImportAttributes::Known(HashMap::new()), }, DependencyDescriptor { kind: DependencyKind::Import, @@ -756,7 +757,7 @@ const d10 = await import("./d10.json", { assert: { type: "json", ["type"]: "bad" span: Span::new(BytePos(583), BytePos(619), Default::default()), specifier: JsWord::from("./d5.json"), specifier_span: Span::new(BytePos(590), BytePos(601), Default::default()), - import_assertions: ImportAssertions::Unknown, + import_attributes: ImportAttributes::Unknown, }, DependencyDescriptor { kind: DependencyKind::Import, @@ -765,7 +766,7 @@ const d10 = await import("./d10.json", { assert: { type: "json", ["type"]: "bad" span: Span::new(BytePos(638), BytePos(681), Default::default()), specifier: JsWord::from("./d6.json"), specifier_span: Span::new(BytePos(645), BytePos(656), Default::default()), - import_assertions: ImportAssertions::Unknown, + import_attributes: ImportAttributes::Unknown, }, DependencyDescriptor { kind: DependencyKind::Import, @@ -774,7 +775,7 @@ const d10 = await import("./d10.json", { assert: { type: "json", ["type"]: "bad" span: Span::new(BytePos(700), BytePos(754), Default::default()), specifier: JsWord::from("./d7.json"), specifier_span: Span::new(BytePos(707), BytePos(718), Default::default()), - import_assertions: ImportAssertions::Unknown, + import_attributes: ImportAttributes::Unknown, }, DependencyDescriptor { kind: DependencyKind::Import, @@ -783,7 +784,7 @@ const d10 = await import("./d10.json", { assert: { type: "json", ["type"]: "bad" span: Span::new(BytePos(773), BytePos(819), Default::default()), specifier: JsWord::from("./d8.json"), specifier_span: Span::new(BytePos(780), BytePos(791), Default::default()), - import_assertions: ImportAssertions::Known({ + import_attributes: ImportAttributes::Known({ let mut map = HashMap::new(); map.insert("type".to_string(), ImportAssertion::Unknown); map @@ -796,7 +797,7 @@ const d10 = await import("./d10.json", { assert: { type: "json", ["type"]: "bad" span: Span::new(BytePos(838), BytePos(895), Default::default()), specifier: JsWord::from("./d9.json"), specifier_span: Span::new(BytePos(845), BytePos(856), Default::default()), - import_assertions: ImportAssertions::Unknown, + import_attributes: ImportAttributes::Unknown, }, DependencyDescriptor { kind: DependencyKind::Import, @@ -805,9 +806,52 @@ const d10 = await import("./d10.json", { assert: { type: "json", ["type"]: "bad" span: Span::new(BytePos(915), BytePos(982), Default::default()), specifier: JsWord::from("./d10.json"), specifier_span: Span::new(BytePos(922), BytePos(934), Default::default()), - import_assertions: ImportAssertions::Unknown, + import_attributes: ImportAttributes::Unknown, }, ] ); } + + #[test] + fn ts_import_object_lit_property() { + let source = r#" +export declare const SomeValue: typeof Core & import("./a.d.ts").Constructor<{ + paginate: import("./b.d.ts").PaginateInterface; +} & import("./c.d.ts").RestEndpointMethods>; +"#; + let (module, comments) = helper("test.ts", source).unwrap(); + let dependencies = analyze_dependencies(&module, &comments); + assert_eq!( + dependencies, + vec![ + DependencyDescriptor { + kind: DependencyKind::ImportType, + is_dynamic: false, + leading_comments: Vec::new(), + span: Span::new(BytePos(48), BytePos(176), Default::default()), + specifier: JsWord::from("./a.d.ts"), + specifier_span: Span::new(BytePos(55), BytePos(65), Default::default()), + import_attributes: ImportAttributes::None, + }, + DependencyDescriptor { + kind: DependencyKind::ImportType, + is_dynamic: false, + leading_comments: Vec::new(), + span: Span::new(BytePos(95), BytePos(131), Default::default()), + specifier: JsWord::from("./b.d.ts"), + specifier_span: Span::new(BytePos(102), BytePos(112), Default::default()), + import_attributes: ImportAttributes::None, + }, + DependencyDescriptor { + kind: DependencyKind::ImportType, + is_dynamic: false, + leading_comments: Vec::new(), + span: Span::new(BytePos(137), BytePos(175), Default::default()), + specifier: JsWord::from("./c.d.ts"), + specifier_span: Span::new(BytePos(144), BytePos(154), Default::default()), + import_attributes: ImportAttributes::None, + } + ] + ); + } } diff --git a/crates/swc_ecma_loader/src/resolvers/node.rs b/crates/swc_ecma_loader/src/resolvers/node.rs index 94a3468f51..80ade412a9 100644 --- a/crates/swc_ecma_loader/src/resolvers/node.rs +++ b/crates/swc_ecma_loader/src/resolvers/node.rs @@ -103,6 +103,7 @@ pub struct NodeModulesResolver { alias: AHashMap, // if true do not resolve symlink preserve_symlinks: bool, + ignore_node_modules: bool, } static EXTENSIONS: &[&str] = &["ts", "tsx", "js", "jsx", "json", "node"]; @@ -118,6 +119,21 @@ impl NodeModulesResolver { target_env, alias, preserve_symlinks, + ignore_node_modules: false, + } + } + + /// Create a node modules resolver which does not care about `node_modules` + pub fn without_node_modules( + target_env: TargetEnv, + alias: AHashMap, + preserve_symlinks: bool, + ) -> Self { + Self { + target_env, + alias, + preserve_symlinks, + ignore_node_modules: true, } } @@ -369,6 +385,10 @@ impl NodeModulesResolver { base_dir: &Path, target: &str, ) -> Result, Error> { + if self.ignore_node_modules { + return Ok(None); + } + let absolute_path = to_absolute_path(base_dir)?; let mut path = Some(&*absolute_path); while let Some(dir) = path { @@ -394,7 +414,7 @@ impl NodeModulesResolver { impl Resolve for NodeModulesResolver { fn resolve(&self, base: &FileName, target: &str) -> Result { debug!( - "Resolve {} from {:#?} for {:#?}", + "Resolving {} from {:#?} for {:#?}", target, base, self.target_env ); diff --git a/crates/swc_ecma_loader/src/resolvers/tsc.rs b/crates/swc_ecma_loader/src/resolvers/tsc.rs index 995afaf87a..357c5550f0 100644 --- a/crates/swc_ecma_loader/src/resolvers/tsc.rs +++ b/crates/swc_ecma_loader/src/resolvers/tsc.rs @@ -1,8 +1,8 @@ -use std::path::{Component, PathBuf}; +use std::path::{Component, Path, PathBuf}; use anyhow::{bail, Context, Error}; use swc_common::FileName; -use tracing::{debug, info, trace, Level}; +use tracing::{debug, info, trace, warn, Level}; use crate::resolve::Resolve; @@ -99,6 +99,58 @@ where paths, } } + + fn invoke_inner_resolver( + &self, + base: &FileName, + module_specifier: &str, + ) -> Result { + let res = self.inner.resolve(base, module_specifier).with_context(|| { + format!( + "failed to resolve `{module_specifier}` from `{base}` using inner \ + resolver\nbase_url={}", + self.base_url_filename + ) + }); + + match res { + Ok(resolved) => { + info!( + "Resolved `{}` as `{}` from `{}`", + module_specifier, resolved, base + ); + + let is_base_in_node_modules = if let FileName::Real(v) = base { + v.components().any(|c| match c { + Component::Normal(v) => v == "node_modules", + _ => false, + }) + } else { + false + }; + let is_target_in_node_modules = if let FileName::Real(v) = &resolved { + v.components().any(|c| match c { + Component::Normal(v) => v == "node_modules", + _ => false, + }) + } else { + false + }; + + // If node_modules is in path, we should return module specifier. + if !is_base_in_node_modules && is_target_in_node_modules { + return Ok(FileName::Real(module_specifier.into())); + } + + Ok(resolved) + } + + Err(err) => { + warn!("{:?}", err); + Err(err) + } + } + } } impl Resolve for TsConfigResolver @@ -110,7 +162,7 @@ where Some( tracing::span!( Level::ERROR, - "tsc.resolve", + "TsConfigResolver::resolve", base_url = tracing::field::display(self.base_url.display()), base = tracing::field::display(base), src = tracing::field::display(module_specifier), @@ -127,30 +179,29 @@ where || module_specifier.starts_with("../")) { return self - .inner - .resolve(base, module_specifier) + .invoke_inner_resolver(base, module_specifier) .context("not processed by tsc resolver because it's relative import"); } - if cfg!(debug_assertions) { - debug!("non-relative import"); - } - if let FileName::Real(v) = base { if v.components().any(|c| match c { Component::Normal(v) => v == "node_modules", _ => false, }) { - return self.inner.resolve(base, module_specifier).context( + return self.invoke_inner_resolver(base, module_specifier).context( "not processed by tsc resolver because base module is in node_modules", ); } } + info!("Checking `jsc.paths`"); + // https://www.typescriptlang.org/docs/handbook/module-resolution.html#path-mapping for (from, to) in &self.paths { match from { Pattern::Wildcard { prefix } => { + debug!("Checking `{}` in `jsc.paths`", prefix); + let extra = module_specifier.strip_prefix(prefix); let extra = match extra { Some(v) => v, @@ -163,35 +214,53 @@ where }; if cfg!(debug_assertions) { - trace!("extra = {}", extra); + debug!("Extra: `{}`", extra); } let mut errors = vec![]; for target in to { let mut replaced = target.replace('*', extra); - let rel = format!("./{}", replaced); - let res = self.inner.resolve(base, &rel).with_context(|| { - format!( - "failed to resolve `{}`, which is expanded from `{}`", - replaced, module_specifier + let _tracing = if cfg!(debug_assertions) { + Some( + tracing::span!( + Level::ERROR, + "TsConfigResolver::resolve::jsc.paths", + replaced = tracing::field::display(&replaced), + ) + .entered(), ) - }); + } else { + None + }; + + let relative = format!("./{}", replaced); + + let res = self + .invoke_inner_resolver(base, module_specifier) + .or_else(|_| { + self.invoke_inner_resolver(&self.base_url_filename, &relative) + }) + .or_else(|_| { + self.invoke_inner_resolver(&self.base_url_filename, &replaced) + }); errors.push(match res { - Ok(v) => return Ok(v), + Ok(resolved) => return Ok(resolved), Err(err) => err, }); if cfg!(target_os = "windows") { - if replaced.starts_with("./") { - replaced = replaced[2..].to_string(); - } replaced = replaced.replace('/', "\\"); } if to.len() == 1 { - return Ok(FileName::Real(self.base_url.join(replaced))); + info!( + "Using `{}` for `{}` because the length of the jsc.paths entry is \ + 1", + replaced, module_specifier + ); + return Ok(FileName::Real(replaced.into())); } } @@ -204,33 +273,29 @@ where } Pattern::Exact(from) => { // Should be exactly matched - if module_specifier == from { - let replaced = self.base_url.join(&to[0]); - if replaced.exists() { - return Ok(FileName::Real(replaced)); - } + if module_specifier != from { + continue; + } - return self - .inner - .resolve(base, &format!("./{}", &to[0])) - .with_context(|| { - format!( - "tried to resolve `{}` because `{}` was exactly matched", - to[0], from - ) - }); + let tp = Path::new(&to[0]); + if tp.is_absolute() { + return Ok(FileName::Real(tp.into())); } + + if let Ok(res) = self.resolve(&self.base_url_filename, &format!("./{}", &to[0])) + { + return Ok(res); + } + + return Ok(FileName::Real(self.base_url.join(&to[0]))); } } } - if let Ok(v) = self - .inner - .resolve(&self.base_url_filename, module_specifier) - { + if let Ok(v) = self.invoke_inner_resolver(&self.base_url_filename, module_specifier) { return Ok(v); } - self.inner.resolve(base, module_specifier) + self.invoke_inner_resolver(base, module_specifier) } } diff --git a/crates/swc_ecma_loader/tests/tsc_resolver.rs b/crates/swc_ecma_loader/tests/tsc_resolver.rs index 220563e77e..5ff4cd0c68 100644 --- a/crates/swc_ecma_loader/tests/tsc_resolver.rs +++ b/crates/swc_ecma_loader/tests/tsc_resolver.rs @@ -38,14 +38,8 @@ fn exact() { } { - let err = r - .resolve(&FileName::Anon, "unrelated") + r.resolve(&FileName::Anon, "unrelated") .expect_err("should not touch error"); - - assert!( - err.source().is_none(), - "should not touch error if src is not related" - ); } } diff --git a/crates/swc_ecma_minifier/benches/full.rs b/crates/swc_ecma_minifier/benches/full.rs index f0c0935256..17eba4f4e7 100644 --- a/crates/swc_ecma_minifier/benches/full.rs +++ b/crates/swc_ecma_minifier/benches/full.rs @@ -112,10 +112,7 @@ fn print(cm: Lrc, nodes: &[N], minify: boo { let mut emitter = swc_ecma_codegen::Emitter { - cfg: swc_ecma_codegen::Config { - minify, - ..Default::default() - }, + cfg: swc_ecma_codegen::Config::default().with_minify(minify), cm: cm.clone(), comments: None, wr: Box::new(JsWriter::new(cm, "\n", &mut buf, None)), diff --git a/crates/swc_ecma_minifier/examples/compress.rs b/crates/swc_ecma_minifier/examples/compress.rs index f141fc2345..705ac90247 100644 --- a/crates/swc_ecma_minifier/examples/compress.rs +++ b/crates/swc_ecma_minifier/examples/compress.rs @@ -75,10 +75,7 @@ fn print(cm: Lrc, nodes: &[N], minify: boo { let mut emitter = swc_ecma_codegen::Emitter { - cfg: swc_ecma_codegen::Config { - minify, - ..Default::default() - }, + cfg: swc_ecma_codegen::Config::default().with_minify(minify), cm: cm.clone(), comments: None, wr: Box::new(JsWriter::new(cm, "\n", &mut buf, None)), diff --git a/crates/swc_ecma_minifier/examples/minifier.rs b/crates/swc_ecma_minifier/examples/minifier.rs index 73708705cb..395f6ebdd2 100644 --- a/crates/swc_ecma_minifier/examples/minifier.rs +++ b/crates/swc_ecma_minifier/examples/minifier.rs @@ -76,10 +76,7 @@ fn print(cm: Lrc, nodes: &[N], minify: boo { let mut emitter = swc_ecma_codegen::Emitter { - cfg: swc_ecma_codegen::Config { - minify, - ..Default::default() - }, + cfg: swc_ecma_codegen::Config::default().with_minify(minify), cm: cm.clone(), comments: None, wr: omit_trailing_semi(JsWriter::new(cm, "\n", &mut buf, None)), diff --git a/crates/swc_ecma_minifier/examples/minify-all.rs b/crates/swc_ecma_minifier/examples/minify-all.rs index a770bc11b0..6a94bbcecc 100644 --- a/crates/swc_ecma_minifier/examples/minify-all.rs +++ b/crates/swc_ecma_minifier/examples/minify-all.rs @@ -122,10 +122,7 @@ fn print(cm: Lrc, nodes: &[N], minify: boo { let mut emitter = swc_ecma_codegen::Emitter { - cfg: swc_ecma_codegen::Config { - minify, - ..Default::default() - }, + cfg: swc_ecma_codegen::Config::default().with_minify(minify), cm: cm.clone(), comments: None, wr: Box::new(JsWriter::new(cm, "\n", &mut buf, None)), diff --git a/crates/swc_ecma_minifier/src/compress/optimize/dead_code.rs b/crates/swc_ecma_minifier/src/compress/optimize/dead_code.rs index 79f94304d1..614a2fc5a4 100644 --- a/crates/swc_ecma_minifier/src/compress/optimize/dead_code.rs +++ b/crates/swc_ecma_minifier/src/compress/optimize/dead_code.rs @@ -47,7 +47,6 @@ impl Optimizer<'_> { // We only handle identifiers on lhs for now. if let Some(lhs) = assign.left.as_ident() { - // if self .data .vars diff --git a/crates/swc_ecma_minifier/src/compress/optimize/evaluate.rs b/crates/swc_ecma_minifier/src/compress/optimize/evaluate.rs index d671f5ef38..77aa659d64 100644 --- a/crates/swc_ecma_minifier/src/compress/optimize/evaluate.rs +++ b/crates/swc_ecma_minifier/src/compress/optimize/evaluate.rs @@ -40,7 +40,7 @@ impl Optimizer<'_> { let usage = self.data.vars.get(&obj.to_id())?; - if usage.reassigned() { + if usage.reassigned { return None; } diff --git a/crates/swc_ecma_minifier/src/compress/optimize/iife.rs b/crates/swc_ecma_minifier/src/compress/optimize/iife.rs index 21063b5f71..7b7eeece51 100644 --- a/crates/swc_ecma_minifier/src/compress/optimize/iife.rs +++ b/crates/swc_ecma_minifier/src/compress/optimize/iife.rs @@ -181,7 +181,7 @@ impl Optimizer<'_> { match &mut **param { Pat::Ident(param) => { if let Some(usage) = self.data.vars.get(¶m.to_id()) { - if usage.reassigned() { + if usage.reassigned { continue; } if usage.ref_count != 1 { @@ -222,7 +222,7 @@ impl Optimizer<'_> { Pat::Rest(rest_pat) => { if let Pat::Ident(param_id) = &*rest_pat.arg { if let Some(usage) = self.data.vars.get(¶m_id.to_id()) { - if usage.reassigned() + if usage.reassigned || usage.ref_count != 1 || !usage.has_property_access { @@ -906,7 +906,7 @@ impl Optimizer<'_> { if let Some(arg) = arg { if let Some(usage) = self.data.vars.get(&orig_params[idx].to_id()) { if usage.ref_count == 1 - && !usage.reassigned() + && !usage.reassigned && !usage.has_property_mutation && matches!( &*arg, diff --git a/crates/swc_ecma_minifier/src/compress/optimize/inline.rs b/crates/swc_ecma_minifier/src/compress/optimize/inline.rs index 2e12bdb081..0bd90c936e 100644 --- a/crates/swc_ecma_minifier/src/compress/optimize/inline.rs +++ b/crates/swc_ecma_minifier/src/compress/optimize/inline.rs @@ -98,9 +98,7 @@ impl Optimizer<'_> { // // TODO: Allow `length` in usage.accessed_props if usage.declared - && !usage.reassigned() - && !usage.mutated - && !usage.has_property_mutation + && !usage.mutated() && usage.accessed_props.is_empty() && !usage.is_infected() && is_inline_enabled @@ -153,7 +151,7 @@ impl Optimizer<'_> { } } - if !usage.reassigned() { + if !usage.reassigned { match init { Expr::Fn(..) | Expr::Arrow(..) => { self.typeofs.insert(ident.to_id(), js_word!("function")); @@ -165,7 +163,7 @@ impl Optimizer<'_> { } } - if !usage.mutated { + if !usage.mutated() { self.mode.store(ident.to_id(), &*init); } @@ -177,7 +175,8 @@ impl Optimizer<'_> { // new variant is added for multi inline, think carefully if is_inline_enabled && usage.declared_count == 1 - && usage.can_inline_var() + && usage.assign_count == 0 + && (!usage.has_property_mutation || !usage.reassigned) && match init { Expr::Ident(Ident { sym: js_word!("eval"), @@ -188,7 +187,7 @@ impl Optimizer<'_> { if !usage.assigned_fn_local { false } else if let Some(u) = self.data.vars.get(&id.to_id()) { - let mut should_inline = !u.reassigned() && u.declared; + let mut should_inline = !u.reassigned && u.declared; should_inline &= // Function declarations are hoisted @@ -322,8 +321,8 @@ impl Optimizer<'_> { && is_inline_enabled && usage.declared && may_remove - && !usage.reassigned() - && (usage.can_inline_var() || usage.is_mutated_only_by_one_call()) + && !usage.reassigned + && usage.assign_count == 0 && ref_count == 1 { match init { @@ -371,7 +370,7 @@ impl Optimizer<'_> { continue; } if let Some(v_usage) = self.data.vars.get(&id) { - if v_usage.reassigned() { + if v_usage.reassigned { return; } } else { @@ -388,7 +387,7 @@ impl Optimizer<'_> { continue; } if let Some(v_usage) = self.data.vars.get(&id) { - if v_usage.reassigned() { + if v_usage.reassigned { return; } } else { @@ -400,7 +399,7 @@ impl Optimizer<'_> { Expr::Object(..) if self.options.pristine_globals => { for id in idents_used_by_ignoring_nested(init) { if let Some(v_usage) = self.data.vars.get(&id) { - if v_usage.reassigned() { + if v_usage.reassigned { return; } } @@ -413,7 +412,7 @@ impl Optimizer<'_> { } if let Some(init_usage) = self.data.vars.get(&id.to_id()) { - if init_usage.reassigned() || !init_usage.declared { + if init_usage.reassigned || !init_usage.declared { return; } } @@ -422,7 +421,7 @@ impl Optimizer<'_> { _ => { for id in idents_used_by(init) { if let Some(v_usage) = self.data.vars.get(&id) { - if v_usage.reassigned() || v_usage.has_property_mutation { + if v_usage.reassigned || v_usage.has_property_mutation { return; } } @@ -534,7 +533,7 @@ impl Optimizer<'_> { } if let Some(usage) = self.data.vars.get(&i.to_id()) { - if !usage.reassigned() { + if !usage.reassigned { trace_op!("typeofs: Storing typeof `{}{:?}`", i.sym, i.span.ctxt); match &*decl { Decl::Fn(..) => { @@ -601,10 +600,10 @@ impl Optimizer<'_> { return; } - if usage.reassigned() || usage.inline_prevented { + if usage.reassigned || usage.inline_prevented { log_abort!( "inline: [x] reassigned = {}, inline_prevented = {}", - usage.reassigned(), + usage.reassigned, usage.inline_prevented ); return; diff --git a/crates/swc_ecma_minifier/src/compress/optimize/mod.rs b/crates/swc_ecma_minifier/src/compress/optimize/mod.rs index cabd31c354..f9f5ce88db 100644 --- a/crates/swc_ecma_minifier/src/compress/optimize/mod.rs +++ b/crates/swc_ecma_minifier/src/compress/optimize/mod.rs @@ -318,6 +318,10 @@ impl From<&Function> for FnMetadata { impl Optimizer<'_> { fn may_remove_ident(&self, id: &Ident) -> bool { + if self.ctx.is_exported { + return false; + } + if id.span.ctxt != self.marks.top_level_ctxt { return true; } @@ -842,7 +846,7 @@ impl Optimizer<'_> { if let Expr::Ident(callee) = &**callee { if self.options.reduce_vars && self.options.side_effects { if let Some(usage) = self.data.vars.get(&callee.to_id()) { - if !usage.reassigned() && usage.pure_fn { + if !usage.reassigned && usage.pure_fn { self.changed = true; report_change!("Reducing function call to a variable"); diff --git a/crates/swc_ecma_minifier/src/compress/optimize/props.rs b/crates/swc_ecma_minifier/src/compress/optimize/props.rs index fd8fcba54b..fc529c4b5a 100644 --- a/crates/swc_ecma_minifier/src/compress/optimize/props.rs +++ b/crates/swc_ecma_minifier/src/compress/optimize/props.rs @@ -29,13 +29,11 @@ impl Optimizer<'_> { .vars .get(&name.to_id()) .map(|v| { - !v.mutated - && v.mutation_by_call_count == 0 + !v.mutated() && !v.used_as_ref && !v.used_as_arg && !v.used_in_cond && (!v.is_fn_local || !self.mode.should_be_very_correct()) - && !v.reassigned() && !v.is_infected() }) .unwrap_or(false) @@ -153,8 +151,7 @@ impl Optimizer<'_> { .map(|v| { v.ref_count == 1 && v.has_property_access - && !v.mutated - && v.mutation_by_call_count == 0 + && !v.mutated() && v.is_fn_local && !v.executed_multiple_time && !v.used_as_arg diff --git a/crates/swc_ecma_minifier/src/compress/optimize/sequences.rs b/crates/swc_ecma_minifier/src/compress/optimize/sequences.rs index bf24de9006..aa2d29ec36 100644 --- a/crates/swc_ecma_minifier/src/compress/optimize/sequences.rs +++ b/crates/swc_ecma_minifier/src/compress/optimize/sequences.rs @@ -1469,7 +1469,7 @@ impl Optimizer<'_> { } _ => a.may_have_side_effects(&self.expr_ctx), }; - if has_side_effect && !usgae.is_fn_local && (usgae.exported || usgae.reassigned()) { + if has_side_effect && !usgae.is_fn_local && (usgae.exported || usgae.reassigned) { log_abort!("a (expr) has side effect"); return false; } @@ -1478,7 +1478,7 @@ impl Optimizer<'_> { if let Some(init) = &a.init { if init.may_have_side_effects(&self.expr_ctx) && !usgae.is_fn_local - && (usgae.exported || usgae.reassigned()) + && (usgae.exported || usgae.reassigned) { log_abort!("a (var) init has side effect"); return false; @@ -2210,7 +2210,7 @@ impl Optimizer<'_> { } // We can remove this variable same as unused pass - if !usage.reassigned() + if !usage.reassigned && usage.usage_count == 1 && usage.declared && !usage.used_recursively @@ -2239,7 +2239,7 @@ impl Optimizer<'_> { _ => false, }; - if usage.ref_count != 1 || usage.reassigned() || !usage.is_fn_local { + if usage.ref_count != 1 || usage.reassigned || !usage.is_fn_local { if is_lit { can_take_init = false } else { @@ -2271,7 +2271,7 @@ impl Optimizer<'_> { Mergable::FnDecl(a) => { if let Some(usage) = self.data.vars.get(&a.ident.to_id()) { - if usage.ref_count != 1 || usage.reassigned() || !usage.is_fn_local { + if usage.ref_count != 1 || usage.reassigned || !usage.is_fn_local { return Ok(false); } diff --git a/crates/swc_ecma_minifier/src/compress/optimize/unused.rs b/crates/swc_ecma_minifier/src/compress/optimize/unused.rs index 75c5ca591b..6d113adcbf 100644 --- a/crates/swc_ecma_minifier/src/compress/optimize/unused.rs +++ b/crates/swc_ecma_minifier/src/compress/optimize/unused.rs @@ -150,7 +150,7 @@ impl Optimizer<'_> { if let Some(v) = self.data.vars.get(&i.to_id()).cloned() { if v.ref_count == 0 && v.usage_count == 0 - && !v.reassigned() + && !v.reassigned && !v.has_property_mutation && !v.declared_as_catch_param { @@ -215,10 +215,7 @@ impl Optimizer<'_> { return true; } - if !usage.mutated - && !usage.reassigned() - && usage.no_side_effect_for_member_access - { + if !usage.mutated() && usage.no_side_effect_for_member_access { return false; } } diff --git a/crates/swc_ecma_minifier/src/pass/merge_exports.rs b/crates/swc_ecma_minifier/src/pass/merge_exports.rs index 6b9958083b..7e9b0bf5c5 100644 --- a/crates/swc_ecma_minifier/src/pass/merge_exports.rs +++ b/crates/swc_ecma_minifier/src/pass/merge_exports.rs @@ -48,7 +48,7 @@ impl VisitMut for Merger { specifiers: self.specifiers.take(), span: DUMMY_SP, type_only: Default::default(), - asserts: Default::default(), + with: Default::default(), }, ))); } @@ -61,7 +61,7 @@ impl VisitMut for Merger { specifiers: Default::default(), span: DUMMY_SP, type_only: Default::default(), - asserts: Default::default(), + with: Default::default(), }, ))); } diff --git a/crates/swc_ecma_minifier/src/program_data.rs b/crates/swc_ecma_minifier/src/program_data.rs index e258732abe..b43191105a 100644 --- a/crates/swc_ecma_minifier/src/program_data.rs +++ b/crates/swc_ecma_minifier/src/program_data.rs @@ -65,7 +65,6 @@ pub(crate) struct VarUsageInfo { pub(crate) declared_as_for_init: bool, pub(crate) assign_count: u32, - pub(crate) mutation_by_call_count: u32, /// The number of direct and indirect reference to this identifier. /// ## Things to note @@ -74,9 +73,7 @@ pub(crate) struct VarUsageInfo { pub(crate) usage_count: u32, /// The variable itself is assigned after reference. - reassigned: bool, - /// The variable itself or a property of it is modified. - pub(crate) mutated: bool, + pub(crate) reassigned: bool, pub(crate) has_property_access: bool, pub(crate) has_property_mutation: bool, @@ -139,10 +136,8 @@ impl Default for VarUsageInfo { declared_as_fn_expr: Default::default(), declared_as_for_init: Default::default(), assign_count: Default::default(), - mutation_by_call_count: Default::default(), usage_count: Default::default(), reassigned: Default::default(), - mutated: Default::default(), has_property_access: Default::default(), has_property_mutation: Default::default(), exported: Default::default(), @@ -170,31 +165,23 @@ impl Default for VarUsageInfo { } impl VarUsageInfo { - pub(crate) fn is_mutated_only_by_one_call(&self) -> bool { - self.assign_count == 0 && self.mutation_by_call_count == 1 - } - pub(crate) fn is_infected(&self) -> bool { !self.infects_to.is_empty() } - pub(crate) fn reassigned(&self) -> bool { - self.reassigned - || (u32::from(self.var_initialized) - + u32::from(self.declared_as_catch_param) - + u32::from(self.declared_as_fn_param) - + self.assign_count) - > 1 - } - - pub(crate) fn can_inline_var(&self) -> bool { - !self.mutated || (self.assign_count == 0 && !self.reassigned()) + /// The variable itself or a property of it is modified. + pub(crate) fn mutated(&self) -> bool { + self.assign_count > 0 || self.has_property_mutation } pub(crate) fn can_inline_fn_once(&self) -> bool { self.callee_count > 0 || !self.executed_multiple_time && (self.is_fn_local || !self.used_in_non_child_fn) } + + fn initialized(&self) -> bool { + self.var_initialized || self.declared_as_fn_param || self.declared_as_catch_param + } } impl Storage for ProgramData { @@ -235,6 +222,8 @@ impl Storage for ProgramData { || (var_info.var_initialized && !e.get().var_initialized); if var_info.var_initialized { + // If it is inited in some other child scope and also inited in current + // scope if e.get().var_initialized || e.get().ref_count > 0 { e.get_mut().assign_count += 1; e.get_mut().reassigned = true; @@ -247,6 +236,8 @@ impl Storage for ProgramData { // If it is inited in some other child scope, but referenced in // current child scope if !inited && e.get().var_initialized && var_info.ref_count > 0 { + e.get_mut().var_initialized = false; + e.get_mut().assign_count += 1; e.get_mut().reassigned = true } } @@ -255,7 +246,11 @@ impl Storage for ProgramData { e.get_mut().reassigned |= var_info.reassigned; - e.get_mut().mutated |= var_info.mutated; + if var_info.assign_count > 0 { + if e.get().initialized() { + e.get_mut().reassigned = true + } + } e.get_mut().has_property_access |= var_info.has_property_access; e.get_mut().has_property_mutation |= var_info.has_property_mutation; @@ -275,7 +270,6 @@ impl Storage for ProgramData { e.get_mut().executed_multiple_time |= var_info.executed_multiple_time; e.get_mut().used_in_cond |= var_info.used_in_cond; e.get_mut().assign_count += var_info.assign_count; - e.get_mut().mutation_by_call_count += var_info.mutation_by_call_count; e.get_mut().usage_count += var_info.usage_count; e.get_mut().infects_to.extend(var_info.infects_to); @@ -314,7 +308,7 @@ impl Storage for ProgramData { } } ScopeKind::Block => { - if var_info.used_in_non_child_fn { + if e.get().used_in_non_child_fn { e.get_mut().is_fn_local = false; e.get_mut().used_in_non_child_fn = true; } @@ -354,13 +348,13 @@ impl Storage for ProgramData { let v = self.vars.entry(i.to_id()).or_default(); v.is_top_level |= ctx.is_top_level; - if has_init && (v.declared || v.var_initialized) { + // assigned or declared before this declaration + if has_init && (v.declared || v.var_initialized || v.assign_count > 0) { #[cfg(feature = "debug")] { tracing::trace!("declare_decl(`{}`): Already declared", i); } - v.mutated = true; v.reassigned = true; v.assign_count += 1; } @@ -396,7 +390,7 @@ impl Storage for ProgramData { self.initialized_vars.truncate(len) } - fn mark_property_mutattion(&mut self, id: Id, ctx: Ctx) { + fn mark_property_mutation(&mut self, id: Id, ctx: Ctx) { let e = self.vars.entry(id).or_default(); e.has_property_mutation = true; @@ -482,14 +476,6 @@ impl VarDataLike for VarUsageInfo { *self.accessed_props.entry(name).or_default() += 1; } - fn mark_mutated(&mut self) { - self.mutated = true; - } - - fn mark_reassigned(&mut self) { - self.reassigned = true; - } - fn mark_used_as_ref(&mut self) { self.used_as_ref = true; } @@ -612,46 +598,45 @@ impl ProgramData { let call_may_mutate = ctx.in_call_arg_of == Some(CalleeKind::Unknown); - // Passing object as a argument is possibly modification. - e.mutated |= is_modify || (call_may_mutate && ctx.is_exact_arg); - e.executed_multiple_time |= ctx.executed_multiple_time; e.used_in_cond |= ctx.in_cond; if is_modify && ctx.is_exact_reassignment { if is_first { + if e.assign_count > 0 || e.initialized() { + e.reassigned = true + } + e.assign_count += 1; + + if !ctx.is_op_assign { + if e.ref_count == 1 + && ctx.in_assign_lhs + && e.var_kind != Some(VarDeclKind::Const) + && !inited + { + self.initialized_vars.insert(i.clone()); + e.assign_count -= 1; + e.var_initialized = true; + } else { + e.reassigned = true + } + } } if ctx.is_op_assign { e.usage_count += 1; - } else if is_first { - if e.ref_count == 1 - && ctx.in_assign_lhs - && e.var_kind != Some(VarDeclKind::Const) - && !inited - { - self.initialized_vars.insert(i.clone()); - e.assign_count -= 1; - e.var_initialized = true; - } else { - e.reassigned = true - } } for other in e.infects_to.clone() { self.report(other.0, ctx, true, dejavu) } } else { - if call_may_mutate && ctx.is_exact_arg { - e.mutation_by_call_count += 1; - } - e.usage_count += 1; } if call_may_mutate && ctx.is_exact_arg { - self.mark_property_mutattion(i, ctx) + self.mark_property_mutation(i, ctx) } } } diff --git a/crates/swc_ecma_minifier/src/util/base54.rs b/crates/swc_ecma_minifier/src/util/base54.rs index 7b0820a6f8..d98dfa98b9 100644 --- a/crates/swc_ecma_minifier/src/util/base54.rs +++ b/crates/swc_ecma_minifier/src/util/base54.rs @@ -227,12 +227,9 @@ impl CharFreq { { let mut emitter = Emitter { - cfg: swc_ecma_codegen::Config { - target: EsVersion::latest(), - ascii_only: false, - minify: true, - ..Default::default() - }, + cfg: swc_ecma_codegen::Config::default() + .with_target(EsVersion::latest()) + .with_minify(true), cm, comments: None, wr: &mut freq, diff --git a/crates/swc_ecma_minifier/tests/compress.rs b/crates/swc_ecma_minifier/tests/compress.rs index c3ba509b84..1ac887200a 100644 --- a/crates/swc_ecma_minifier/tests/compress.rs +++ b/crates/swc_ecma_minifier/tests/compress.rs @@ -630,10 +630,7 @@ fn print( } let mut emitter = Emitter { - cfg: swc_ecma_codegen::Config { - minify, - ..Default::default() - }, + cfg: swc_ecma_codegen::Config::default().with_minify(minify), cm, comments: None, wr, diff --git a/crates/swc_ecma_minifier/tests/exec.rs b/crates/swc_ecma_minifier/tests/exec.rs index 98fab543f6..a02d73b4c2 100644 --- a/crates/swc_ecma_minifier/tests/exec.rs +++ b/crates/swc_ecma_minifier/tests/exec.rs @@ -77,10 +77,7 @@ fn print( } let mut emitter = Emitter { - cfg: swc_ecma_codegen::Config { - minify, - ..Default::default() - }, + cfg: swc_ecma_codegen::Config::default().with_minify(minify), cm, comments: None, wr, diff --git a/crates/swc_ecma_minifier/tests/mangle.rs b/crates/swc_ecma_minifier/tests/mangle.rs index 480f48248e..140f3c16d3 100644 --- a/crates/swc_ecma_minifier/tests/mangle.rs +++ b/crates/swc_ecma_minifier/tests/mangle.rs @@ -33,10 +33,7 @@ fn print(cm: Lrc, m: &Module, minify: bool) -> String { } let mut emitter = Emitter { - cfg: swc_ecma_codegen::Config { - minify, - ..Default::default() - }, + cfg: swc_ecma_codegen::Config::default().with_minify(minify), cm, comments: None, wr, diff --git a/crates/swc_ecma_minifier/tests/terser_exec.rs b/crates/swc_ecma_minifier/tests/terser_exec.rs index dd8d2ed84a..df809bf1af 100644 --- a/crates/swc_ecma_minifier/tests/terser_exec.rs +++ b/crates/swc_ecma_minifier/tests/terser_exec.rs @@ -304,10 +304,7 @@ fn print( } let mut emitter = Emitter { - cfg: swc_ecma_codegen::Config { - minify, - ..Default::default() - }, + cfg: swc_ecma_codegen::Config::default().with_minify(minify), cm, comments: None, wr, diff --git a/crates/swc_ecma_parser/src/lexer/jsx.rs b/crates/swc_ecma_parser/src/lexer/jsx.rs index ea64b0c188..e10f0870e6 100644 --- a/crates/swc_ecma_parser/src/lexer/jsx.rs +++ b/crates/swc_ecma_parser/src/lexer/jsx.rs @@ -33,12 +33,18 @@ impl<'a> Lexer<'a> { // if cur_pos == self.state.start { if cur == '<' && self.state.is_expr_allowed { - self.input.bump(); + unsafe { + // Safety: cur() was Some('<') + self.input.bump(); + } return Ok(Token::JSXTagStart).map(Some); } return self.read_token(); } - out.push_str(self.input.slice(chunk_start, cur_pos)); + out.push_str(unsafe { + // Safety: We already checked for the range + self.input.slice(chunk_start, cur_pos) + }); return Ok(Token::JSXText { raw: Atom::new(out), @@ -52,7 +58,10 @@ impl<'a> Lexer<'a> { candidate_list: vec!["`{'>'}`", "`>`"], }, ); - self.input.bump() + unsafe { + // Safety: cur() was Some('>') + self.input.bump() + } } '}' => { self.emit_error( @@ -61,10 +70,16 @@ impl<'a> Lexer<'a> { candidate_list: vec!["`{'}'}`", "`}`"], }, ); - self.input.bump() + unsafe { + // Safety: cur() was Some('}') + self.input.bump() + } } '&' => { - out.push_str(self.input.slice(chunk_start, cur_pos)); + out.push_str(unsafe { + // Safety: We already checked for the range + self.input.slice(chunk_start, cur_pos) + }); let jsx_entity = self.read_jsx_entity()?; @@ -74,14 +89,20 @@ impl<'a> Lexer<'a> { _ => { if cur.is_line_terminator() { - out.push_str(self.input.slice(chunk_start, cur_pos)); + out.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) => out.push_str(s), Either::Right(c) => out.push(c), } chunk_start = cur_pos; } else { - self.input.bump() + unsafe { + // Safety: cur() was Some(c) + self.input.bump() + } } } } @@ -113,7 +134,10 @@ impl<'a> Lexer<'a> { let c = self.input.cur(); debug_assert_eq!(c, Some('&')); - self.input.bump(); + unsafe { + // Safety: cur() was Some('&') + self.input.bump(); + } let start_pos = self.input.cur_pos(); @@ -122,7 +146,10 @@ impl<'a> Lexer<'a> { Some(c) => c, None => break, }; - self.input.bump(); + unsafe { + // Safety: cur() was Some(c) + self.input.bump(); + } if c == ';' { if let Some(stripped) = s.strip_prefix('#') { @@ -147,7 +174,10 @@ impl<'a> Lexer<'a> { s.push(c) } - self.input.reset_to(start_pos); + unsafe { + // Safety: start_pos is a valid position because we got it from self.input + self.input.reset_to(start_pos); + } Ok(('&', "&".to_string())) } @@ -159,10 +189,16 @@ impl<'a> Lexer<'a> { debug_assert!(self.syntax.jsx()); let ch = self.input.cur().unwrap(); - self.input.bump(); + unsafe { + // Safety: cur() was Some(ch) + self.input.bump(); + } let out = if ch == '\r' && self.input.cur() == Some('\n') { - self.input.bump(); + unsafe { + // Safety: cur() was Some('\n') + self.input.bump(); + } Either::Left(if normalize_crlf { "\n" } else { "\r\n" }) } else { Either::Right(ch) @@ -181,7 +217,10 @@ impl<'a> Lexer<'a> { raw.push(quote); - self.input.bump(); // `quote` + unsafe { + // Safety: cur() was Some(quote) + self.input.bump(); // `quote` + } let mut out = String::new(); let mut chunk_start = self.input.cur_pos(); @@ -199,7 +238,10 @@ impl<'a> Lexer<'a> { let cur_pos = self.input.cur_pos(); if ch == '\\' { - let value = self.input.slice(chunk_start, cur_pos); + let value = unsafe { + // Safety: We already checked for the range + self.input.slice(chunk_start, cur_pos) + }; out.push_str(value); out.push('\\'); @@ -218,7 +260,10 @@ impl<'a> Lexer<'a> { } if ch == '&' { - let value = self.input.slice(chunk_start, cur_pos); + let value = unsafe { + // Safety: We already checked for the range + self.input.slice(chunk_start, cur_pos) + }; out.push_str(value); raw.push_str(value); @@ -230,7 +275,10 @@ impl<'a> Lexer<'a> { chunk_start = self.input.cur_pos(); } else if ch.is_line_terminator() { - let value = self.input.slice(chunk_start, cur_pos); + let value = unsafe { + // Safety: We already checked for the range + self.input.slice(chunk_start, cur_pos) + }; out.push_str(value); raw.push_str(value); @@ -248,12 +296,18 @@ impl<'a> Lexer<'a> { chunk_start = cur_pos + BytePos(ch.len_utf8() as _); } else { - self.input.bump(); + unsafe { + // Safety: cur() was Some(ch) + self.input.bump(); + } } } let cur_pos = self.input.cur_pos(); - let value = self.input.slice(chunk_start, cur_pos); + let value = unsafe { + // Safety: We already checked for the range + self.input.slice(chunk_start, cur_pos) + }; out.push_str(value); raw.push_str(value); @@ -261,7 +315,10 @@ impl<'a> Lexer<'a> { // it might be at the end of the file when // the string literal is unterminated if self.input.peek_ahead().is_some() { - self.input.bump(); + unsafe { + // Safety: We called peek_ahead() which means cur() was Some + self.input.bump(); + } } raw.push(quote); diff --git a/crates/swc_ecma_parser/src/lexer/mod.rs b/crates/swc_ecma_parser/src/lexer/mod.rs index 387389f9b1..992a8b2c21 100644 --- a/crates/swc_ecma_parser/src/lexer/mod.rs +++ b/crates/swc_ecma_parser/src/lexer/mod.rs @@ -201,7 +201,10 @@ impl<'a> Lexer<'a> { fn read_token_number_sign(&mut self) -> LexResult> { debug_assert!(self.cur().is_some()); - self.input.bump(); // '#' + unsafe { + // Safety: cur() is Some('#') + self.input.bump(); // '#' + } // `#` can also be a part of shebangs, however they should have been // handled by `read_shebang()` @@ -221,7 +224,10 @@ impl<'a> Lexer<'a> { let next = match self.input.peek() { Some(next) => next, None => { - self.input.bump(); + unsafe { + // Safety: cur() is Some(',') + self.input.bump(); + } return Ok(tok!('.')); } }; @@ -232,11 +238,19 @@ impl<'a> Lexer<'a> { }); } - self.input.bump(); // 1st `.` + unsafe { + // Safety: cur() is Some + // 1st `.` + self.input.bump(); + } if next == '.' && self.input.peek() == Some('.') { - self.input.bump(); // 2nd `.` - self.input.bump(); // 3rd `.` + unsafe { + // Safety: peek() was Some + + self.input.bump(); // 2nd `.` + self.input.bump(); // 3rd `.` + } return Ok(tok!("...")); } @@ -251,16 +265,26 @@ impl<'a> Lexer<'a> { fn read_token_question_mark(&mut self) -> LexResult { match self.input.peek() { Some('?') => { - self.input.bump(); - self.input.bump(); - if self.input.cur() == Some('=') { + unsafe { + // Safety: peek() was some + self.input.bump(); self.input.bump(); + } + if self.input.cur() == Some('=') { + unsafe { + // Safety: cur() was some + self.input.bump(); + } + return Ok(tok!("??=")); } Ok(tok!("??")) } _ => { - self.input.bump(); + unsafe { + // Safety: peek() is callable only if cur() is Some + self.input.bump(); + } Ok(tok!('?')) } } @@ -271,7 +295,10 @@ impl<'a> Lexer<'a> { /// This is extracted as a method to reduce size of `read_token`. #[inline(never)] fn read_token_colon(&mut self) -> LexResult { - self.input.bump(); + unsafe { + // Safety: cur() is Some(':') + self.input.bump(); + } Ok(tok!(':')) } @@ -308,7 +335,10 @@ impl<'a> Lexer<'a> { let had_line_break_before_last = self.had_line_break_before_last(); let start = self.cur_pos(); - self.input.bump(); + unsafe { + // Safety: cur() is Some(c as char) + self.input.bump(); + } let token = if c == b'&' { BitAnd } else { BitOr }; // '|=', '&=' @@ -322,10 +352,16 @@ impl<'a> Lexer<'a> { // '||', '&&' if self.input.cur() == Some(c as char) { - self.input.bump(); + unsafe { + // Safety: cur() is Some(c) + self.input.bump(); + } if self.input.cur() == Some('=') { - self.input.bump(); + unsafe { + // Safety: cur() is Some('=') + self.input.bump(); + } return Ok(AssignOp(match token { BitAnd => op!("&&="), BitOr => op!("||="), @@ -359,7 +395,10 @@ impl<'a> Lexer<'a> { #[inline(never)] fn read_token_mul_mod(&mut self, c: u8) -> LexResult { let is_mul = c == b'*'; - self.input.bump(); + unsafe { + // Safety: cur() is Some(c) + self.input.bump(); + } let mut token = if is_mul { BinOp(Mul) } else { BinOp(Mod) }; // check for ** @@ -521,7 +560,10 @@ impl<'a> Lexer<'a> { } }; - self.input.bump(); + unsafe { + // Safety: cur() is Some(c) if this method is called. + self.input.bump(); + } Ok(Some(vec![c.into()])) } @@ -529,11 +571,17 @@ impl<'a> Lexer<'a> { fn read_token_plus_minus(&mut self, c: u8) -> LexResult> { let start = self.cur_pos(); - self.input.bump(); + unsafe { + // Safety: cur() is Some(c), if this method is called. + self.input.bump(); + } // '++', '--' Ok(Some(if self.input.cur() == Some(c as char) { - self.input.bump(); + unsafe { + // Safety: cur() is Some(c) + self.input.bump(); + } // Handle --> if self.state.had_line_break && c == b'-' && self.eat(b'>') { @@ -559,7 +607,10 @@ impl<'a> Lexer<'a> { let start = self.cur_pos(); let had_line_break_before_last = self.had_line_break_before_last(); - self.input.bump(); + unsafe { + // Safety: cur() is Some(c) if this method is called. + self.input.bump(); + } Ok(Some(if self.input.eat_byte(b'=') { // "==" @@ -911,7 +962,10 @@ impl<'a> Lexer<'a> { chars.push(c.into()); } _ => { - self.input.reset_to(state); + unsafe { + // Safety: state is valid position because we got it from cur_pos() + self.input.reset_to(state); + } chars.push(Char::from('\\')); chars.push(Char::from('u')); @@ -1029,7 +1083,10 @@ impl<'a> Lexer<'a> { /// Expects current char to be '/' fn read_regexp(&mut self, start: BytePos) -> LexResult { - self.input.reset_to(start); + unsafe { + // Safety: start is valid position, and cur() is Some('/') + self.input.reset_to(start); + } debug_assert_eq!(self.cur(), Some('/')); @@ -1104,8 +1161,12 @@ impl<'a> Lexer<'a> { if self.input.cur() != Some('#') || self.input.peek() != Some('!') { return Ok(None); } - self.input.bump(); - self.input.bump(); + unsafe { + // Safety: cur() is Some('#') + self.input.bump(); + // Safety: cur() is Some('!') + self.input.bump(); + } let s = self.input.uncons_while(|c| !c.is_line_terminator()); Ok(Some(Atom::new(s))) } diff --git a/crates/swc_ecma_parser/src/lexer/number.rs b/crates/swc_ecma_parser/src/lexer/number.rs index 67194ca496..e2df37e195 100644 --- a/crates/swc_ecma_parser/src/lexer/number.rs +++ b/crates/swc_ecma_parser/src/lexer/number.rs @@ -491,7 +491,10 @@ impl<'a> Lexer<'a> { } // Ignore this _ character - self.input.bump(); + unsafe { + // Safety: cur() returns Some(c) where c is a valid char + self.input.bump(); + } raw.push(c); continue; diff --git a/crates/swc_ecma_parser/src/lexer/state.rs b/crates/swc_ecma_parser/src/lexer/state.rs index c632be1df7..06062d18ef 100644 --- a/crates/swc_ecma_parser/src/lexer/state.rs +++ b/crates/swc_ecma_parser/src/lexer/state.rs @@ -286,7 +286,10 @@ impl<'a> Iterator for Lexer<'a> { } if c == '>' { - self.input.bump(); + unsafe { + // Safety: cur() is Some('>') + self.input.bump(); + } return Ok(Some(Token::JSXTagEnd)); } @@ -301,7 +304,10 @@ impl<'a> Iterator for Lexer<'a> { let had_line_break_before_last = self.had_line_break_before_last(); let cur_pos = self.input.cur_pos(); - self.input.bump(); + unsafe { + // Safety: cur() is Some('<') + self.input.bump(); + } if had_line_break_before_last && self.is_str("<<<<<< ") { let span = Span::new(cur_pos, cur_pos + BytePos(7), Default::default()); diff --git a/crates/swc_ecma_parser/src/lexer/table.rs b/crates/swc_ecma_parser/src/lexer/table.rs index 2e7072e6d5..155c783aa0 100644 --- a/crates/swc_ecma_parser/src/lexer/table.rs +++ b/crates/swc_ecma_parser/src/lexer/table.rs @@ -51,7 +51,10 @@ const ERR: ByteHandler = Some(|lexer| { }; let start = lexer.cur_pos(); - lexer.input.bump(); + unsafe { + // Safety: Byte handler is only called for non-last chracters + lexer.input.bump(); + } lexer.error_span(pos_span(start), SyntaxError::UnexpectedChar { c })? }); @@ -89,7 +92,10 @@ const UNI: ByteHandler = Some(|lexer| { } let start = lexer.cur_pos(); - lexer.input.bump(); + unsafe { + // Safety: Byte handler is only called for non-last chracters + lexer.input.bump(); + } lexer.error_span(pos_span(start), SyntaxError::UnexpectedChar { c })? }); diff --git a/crates/swc_ecma_parser/src/lexer/util.rs b/crates/swc_ecma_parser/src/lexer/util.rs index d31fddc594..694c0b81c2 100644 --- a/crates/swc_ecma_parser/src/lexer/util.rs +++ b/crates/swc_ecma_parser/src/lexer/util.rs @@ -69,7 +69,10 @@ impl<'a> Lexer<'a> { #[inline(always)] pub(super) fn bump(&mut self) { - self.input.bump() + unsafe { + // Safety: Actually this is not safe but this is an internal method. + self.input.bump() + } } #[inline(always)] @@ -246,7 +249,10 @@ impl<'a> Lexer<'a> { let end = self.cur_pos(); if let Some(comments) = self.comments_buffer.as_mut() { - let s = self.input.slice(slice_start, end); + let s = unsafe { + // Safety: We know that the start and the end are valid + self.input.slice(slice_start, end) + }; let cmt = Comment { kind: CommentKind::Line, span: Span::new(start, end, SyntaxContext::empty()), @@ -264,7 +270,10 @@ impl<'a> Lexer<'a> { } } - self.input.reset_to(end); + unsafe { + // Safety: We got end from self.input + self.input.reset_to(end); + } } /// Expects current char to be '/' and next char to be '*'. @@ -302,7 +311,10 @@ impl<'a> Lexer<'a> { } if let Some(comments) = self.comments_buffer.as_mut() { - let src = self.input.slice(slice_start, end); + let src = unsafe { + // Safety: We got slice_start and end from self.input so those are valid. + self.input.slice(slice_start, end) + }; let s = &src[..src.len() - 2]; let cmt = Comment { kind: CommentKind::Block, diff --git a/crates/swc_ecma_parser/src/lib.rs b/crates/swc_ecma_parser/src/lib.rs index fe50ea65dc..415dc49606 100644 --- a/crates/swc_ecma_parser/src/lib.rs +++ b/crates/swc_ecma_parser/src/lib.rs @@ -175,11 +175,11 @@ impl Syntax { } } - pub fn import_assertions(self) -> bool { + pub fn import_attributes(self) -> bool { match self { Syntax::Es(EsConfig { - import_assertions, .. - }) => import_assertions, + import_attributes, .. + }) => import_attributes, Syntax::Typescript(_) => true, } } @@ -282,9 +282,12 @@ impl Syntax { } } - fn using_decl(&self) -> bool { + pub fn explicit_resource_management(&self) -> bool { match self { - Syntax::Es(EsConfig { using_decl, .. }) => *using_decl, + Syntax::Es(EsConfig { + explicit_resource_management: using_decl, + .. + }) => *using_decl, Syntax::Typescript(_) => true, } } @@ -341,8 +344,8 @@ pub struct EsConfig { pub export_default_from: bool, /// Stage 3. - #[serde(default)] - pub import_assertions: bool, + #[serde(default, alias = "importAssertions")] + pub import_attributes: bool, #[serde(default, rename = "allowSuperOutsideMethod")] pub allow_super_outside_method: bool, @@ -354,7 +357,7 @@ pub struct EsConfig { pub auto_accessors: bool, #[serde(default)] - pub using_decl: bool, + pub explicit_resource_management: bool, } /// Syntactic context. diff --git a/crates/swc_ecma_parser/src/parser/expr.rs b/crates/swc_ecma_parser/src/parser/expr.rs index f6d6d4dfac..b077d891f5 100644 --- a/crates/swc_ecma_parser/src/parser/expr.rs +++ b/crates/swc_ecma_parser/src/parser/expr.rs @@ -740,7 +740,7 @@ impl Parser { expect!(p, ','); // Handle trailing comma. if is!(p, ')') { - if is_dynamic_import && !p.input.syntax().import_assertions() { + if is_dynamic_import && !p.input.syntax().import_attributes() { syntax_error!( p, span!(p, start), diff --git a/crates/swc_ecma_parser/src/parser/stmt.rs b/crates/swc_ecma_parser/src/parser/stmt.rs index 273d447843..00f0c164cd 100644 --- a/crates/swc_ecma_parser/src/parser/stmt.rs +++ b/crates/swc_ecma_parser/src/parser/stmt.rs @@ -816,7 +816,7 @@ impl<'a, I: Tokens> Parser { decls.push(self.parse_var_declarator(false, VarDeclKind::Var)?); } - if !self.syntax().using_decl() { + if !self.syntax().explicit_resource_management() { self.emit_err(span!(self, start), SyntaxError::UsingDeclNotEnabled); } @@ -1272,7 +1272,7 @@ impl<'a, I: Tokens> Parser { let start = cur_pos!(self); let init = self.include_in_expr(false).parse_for_head_prefix()?; - let is_using_decl = self.input.syntax().using_decl() + let is_using_decl = self.input.syntax().explicit_resource_management() && match *init { Expr::Ident(Ident { sym: js_word!("using"), @@ -2434,7 +2434,7 @@ export default function waitUntil(callback, options = {}) { test_parser( src, Syntax::Es(EsConfig { - import_assertions: true, + import_attributes: true, ..Default::default() }), |p| p.parse_expr(), diff --git a/crates/swc_ecma_parser/src/parser/stmt/module_item.rs b/crates/swc_ecma_parser/src/parser/stmt/module_item.rs index d2b2003f2d..9ddc999e36 100644 --- a/crates/swc_ecma_parser/src/parser/stmt/module_item.rs +++ b/crates/swc_ecma_parser/src/parser/stmt/module_item.rs @@ -63,9 +63,9 @@ impl Parser { _ => unreachable!(), }; let _ = cur!(self, false); - let asserts = if self.input.syntax().import_assertions() + let with = if self.input.syntax().import_attributes() && !self.input.had_line_break_before_cur() - && eat!(self, "assert") + && (eat!(self, "assert") || eat!(self, "with")) { match *self.parse_object::>()? { Expr::Object(v) => Some(Box::new(v)), @@ -80,7 +80,7 @@ impl Parser { src, specifiers: vec![], type_only: false, - asserts, + with, })) .map(ModuleItem::from); } @@ -157,9 +157,9 @@ impl Parser { }; let _ = cur!(self, false); - let asserts = if self.input.syntax().import_assertions() + let with = if self.input.syntax().import_attributes() && !self.input.had_line_break_before_cur() - && eat!(self, "assert") + && (eat!(self, "assert") || eat!(self, "with")) { match *self.parse_object::>()? { Expr::Object(v) => Some(Box::new(v)), @@ -176,7 +176,7 @@ impl Parser { specifiers, src, type_only, - asserts, + with, })) .map(ModuleItem::from) } @@ -570,12 +570,12 @@ impl Parser { assert_and_bump!(self, '*'); // improve error message for `export * from foo` - let (src, asserts) = self.parse_from_clause_and_semi()?; + let (src, with) = self.parse_from_clause_and_semi()?; return Ok(ModuleDecl::ExportAll(ExportAll { span: span!(self, start), src, type_only, - asserts, + with, })); } @@ -617,13 +617,13 @@ impl Parser { if has_default || has_ns { if is!(self, "from") { - let (src, asserts) = self.parse_from_clause_and_semi()?; + let (src, with) = self.parse_from_clause_and_semi()?; return Ok(ModuleDecl::ExportNamed(NamedExport { span: span!(self, start), specifiers, src: Some(src), type_only, - asserts, + with, })); } else if !self.input.syntax().export_default_from() { // emit error @@ -667,7 +667,7 @@ impl Parser { } None }; - let (src, asserts) = match opt { + let (src, with) = match opt { Some(v) => (Some(v.0), v.1), None => (None, None), }; @@ -676,7 +676,7 @@ impl Parser { specifiers, src, type_only, - asserts, + with, })); }; @@ -793,7 +793,7 @@ impl Parser { }) } - /// Parses `from 'foo.js' assert {};` + /// Parses `from 'foo.js' with {};` or `from 'foo.js' assert {};` fn parse_from_clause_and_semi(&mut self) -> PResult<(Box, Option>)> { expect!(self, "from"); @@ -810,9 +810,9 @@ impl Parser { _ => unexpected!(self, "a string literal"), }; let _ = cur!(self, false); - let asserts = if self.input.syntax().import_assertions() + let with = if self.input.syntax().import_attributes() && !self.input.had_line_break_before_cur() - && eat!(self, "assert") + && (eat!(self, "assert") || eat!(self, "with")) { match *self.parse_object::>()? { Expr::Object(v) => Some(Box::new(v)), @@ -822,7 +822,7 @@ impl Parser { None }; expect!(self, ';'); - Ok((src, asserts)) + Ok((src, with)) } } diff --git a/crates/swc_ecma_parser/tests/comments.rs b/crates/swc_ecma_parser/tests/comments.rs index 397571d2f8..65b7f9b198 100644 --- a/crates/swc_ecma_parser/tests/comments.rs +++ b/crates/swc_ecma_parser/tests/comments.rs @@ -29,7 +29,7 @@ fn test(input: PathBuf) { decorators: true, decorators_before_export: false, export_default_from: true, - import_assertions: true, + import_attributes: true, ..Default::default() }), "ts" | "tsx" => Syntax::Typescript(TsConfig { diff --git a/crates/swc_ecma_parser/tests/errors.rs b/crates/swc_ecma_parser/tests/errors.rs index ff68922564..973b5c9bd1 100644 --- a/crates/swc_ecma_parser/tests/errors.rs +++ b/crates/swc_ecma_parser/tests/errors.rs @@ -47,7 +47,7 @@ where } else { ::swc_ecma_parser::Syntax::Es(::swc_ecma_parser::EsConfig { jsx: is_jsx, - using_decl: true, + explicit_resource_management: true, ..Default::default() }) }; diff --git a/crates/swc_ecma_parser/tests/js.rs b/crates/swc_ecma_parser/tests/js.rs index 507c532da4..a7de707d50 100644 --- a/crates/swc_ecma_parser/tests/js.rs +++ b/crates/swc_ecma_parser/tests/js.rs @@ -91,7 +91,8 @@ where let lexer = Lexer::new( Syntax::Es(EsConfig { - using_decl: true, + explicit_resource_management: true, + import_attributes: true, ..Default::default() }), EsVersion::Es2015, diff --git a/crates/swc_ecma_preset_env/src/lib.rs b/crates/swc_ecma_preset_env/src/lib.rs index 4b14862b72..826b7f4a50 100644 --- a/crates/swc_ecma_preset_env/src/lib.rs +++ b/crates/swc_ecma_preset_env/src/lib.rs @@ -173,10 +173,13 @@ where let pass = add!( pass, OptionalChaining, - es2020::optional_chaining(es2020::optional_chaining::Config { - no_document_all: loose || assumptions.no_document_all, - pure_getter: loose || assumptions.pure_getters - }) + es2020::optional_chaining( + es2020::optional_chaining::Config { + no_document_all: loose || assumptions.no_document_all, + pure_getter: loose || assumptions.pure_getters + }, + global_mark + ) ); // ES2019 @@ -452,7 +455,7 @@ impl VisitMut for Polyfills { } .into(), type_only: false, - asserts: None, + with: None, })) }), ); @@ -470,7 +473,7 @@ impl VisitMut for Polyfills { } .into(), type_only: false, - asserts: None, + with: None, })) }), ); diff --git a/crates/swc_ecma_preset_env/tests/test.rs b/crates/swc_ecma_preset_env/tests/test.rs index f4bf64eb8c..1379699a01 100644 --- a/crates/swc_ecma_preset_env/tests/test.rs +++ b/crates/swc_ecma_preset_env/tests/test.rs @@ -159,10 +159,7 @@ fn exec(c: PresetConfig, dir: PathBuf) -> Result<(), Error> { let mut buf = vec![]; { let mut emitter = Emitter { - cfg: swc_ecma_codegen::Config { - minify: false, - ..Default::default() - }, + cfg: swc_ecma_codegen::Config::default(), comments: None, cm: cm.clone(), wr: Box::new(swc_ecma_codegen::text_writer::JsWriter::new( diff --git a/crates/swc_ecma_quote_macros/src/ast/module_decl.rs b/crates/swc_ecma_quote_macros/src/ast/module_decl.rs index 56503a53c0..17b2ebcfa1 100644 --- a/crates/swc_ecma_quote_macros/src/ast/module_decl.rs +++ b/crates/swc_ecma_quote_macros/src/ast/module_decl.rs @@ -15,12 +15,12 @@ impl_enum!( ] ); -impl_struct!(ImportDecl, [span, specifiers, src, type_only, asserts]); +impl_struct!(ImportDecl, [span, specifiers, src, type_only, with]); impl_struct!(ExportDecl, [span, decl]); impl_struct!(ExportDefaultDecl, [span, decl]); impl_struct!(ExportDefaultExpr, [span, expr]); -impl_struct!(ExportAll, [span, src, asserts]); -impl_struct!(NamedExport, [span, specifiers, src, type_only, asserts]); +impl_struct!(ExportAll, [span, src, with]); +impl_struct!(NamedExport, [span, specifiers, src, type_only, with]); impl_enum!(ImportSpecifier, [Named, Default, Namespace]); diff --git a/crates/swc_ecma_transforms_base/src/helpers/mod.rs b/crates/swc_ecma_transforms_base/src/helpers/mod.rs index 29e8f5160f..c29b1fe4f4 100644 --- a/crates/swc_ecma_transforms_base/src/helpers/mod.rs +++ b/crates/swc_ecma_transforms_base/src/helpers/mod.rs @@ -86,7 +86,7 @@ macro_rules! add_import_to { span: DUMMY_SP, specifiers: vec![s], src: Box::new(src), - asserts: Default::default(), + with: Default::default(), type_only: Default::default(), }))) } diff --git a/crates/swc_ecma_transforms_base/src/rename/ops.rs b/crates/swc_ecma_transforms_base/src/rename/ops.rs index 6ab3af91ec..c2e35bf962 100644 --- a/crates/swc_ecma_transforms_base/src/rename/ops.rs +++ b/crates/swc_ecma_transforms_base/src/rename/ops.rs @@ -223,7 +223,7 @@ impl<'a> VisitMut for Operator<'a> { })], src: None, type_only: false, - asserts: None, + with: None, }, ))); }; @@ -343,7 +343,7 @@ impl<'a> VisitMut for Operator<'a> { specifiers: renamed, src: None, type_only: false, - asserts: None, + with: None, }, ))); } diff --git a/crates/swc_ecma_transforms_base/src/resolver/mod.rs b/crates/swc_ecma_transforms_base/src/resolver/mod.rs index c947a89cc5..d20afdff4e 100644 --- a/crates/swc_ecma_transforms_base/src/resolver/mod.rs +++ b/crates/swc_ecma_transforms_base/src/resolver/mod.rs @@ -1,5 +1,5 @@ use rustc_hash::FxHashSet; -use swc_atoms::{js_word, JsWord}; +use swc_atoms::JsWord; use swc_common::{ collections::{AHashMap, AHashSet}, Mark, Span, SyntaxContext, @@ -269,11 +269,6 @@ impl<'a> Resolver<'a> { } fn mark_for_ref_inner(&self, sym: &JsWord, stop_an_fn_scope: bool) -> Option { - // NaN always points the globals - if *sym == js_word!("NaN") { - return Some(self.config.unresolved_mark); - } - if self.config.handle_types && self.in_type { let mut mark = self.current.mark; let mut scope = Some(&self.current); @@ -307,7 +302,16 @@ impl<'a> Resolver<'a> { if mark == Mark::root() { return None; } - return Some(mark); + + return match &**sym { + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects#value_properties + "undefined" | "NaN" | "Infinity" | "globalThis" + if mark == self.config.top_level_mark => + { + Some(self.config.unresolved_mark) + } + _ => Some(mark), + }; } if cur.kind == ScopeKind::Fn && stop_an_fn_scope { diff --git a/crates/swc_ecma_transforms_base/tests/fixer_test262.rs b/crates/swc_ecma_transforms_base/tests/fixer_test262.rs index 60822e4bb2..4afd9b651f 100644 --- a/crates/swc_ecma_transforms_base/tests/fixer_test262.rs +++ b/crates/swc_ecma_transforms_base/tests/fixer_test262.rs @@ -175,10 +175,7 @@ fn identity_tests(tests: &mut Vec) -> Result<(), io::Error> { { let mut emitter = Emitter { - cfg: swc_ecma_codegen::Config { - minify: false, - ..Default::default() - }, + cfg: swc_ecma_codegen::Config::default(), cm: cm.clone(), wr: Box::new(swc_ecma_codegen::text_writer::JsWriter::new( cm.clone(), @@ -189,10 +186,7 @@ fn identity_tests(tests: &mut Vec) -> Result<(), io::Error> { comments: None, }; let mut expected_emitter = Emitter { - cfg: swc_ecma_codegen::Config { - minify: false, - ..Default::default() - }, + cfg: swc_ecma_codegen::Config::default(), cm: cm.clone(), wr: Box::new(swc_ecma_codegen::text_writer::JsWriter::new( cm, "\n", &mut wr2, None, diff --git a/crates/swc_ecma_transforms_compat/src/es2015/block_scoping/mod.rs b/crates/swc_ecma_transforms_compat/src/es2015/block_scoping/mod.rs index 4c80ceefd3..ce18711f7e 100644 --- a/crates/swc_ecma_transforms_compat/src/es2015/block_scoping/mod.rs +++ b/crates/swc_ecma_transforms_compat/src/es2015/block_scoping/mod.rs @@ -431,13 +431,6 @@ impl VisitMut for BlockScoping { self.handle_capture_of_vars(&mut node.body); } - fn visit_mut_while_stmt(&mut self, node: &mut WhileStmt) { - self.visit_mut_with_scope(ScopeKind::new_loop(), &mut node.body); - - node.test.visit_mut_with(self); - self.handle_capture_of_vars(&mut node.body); - } - fn visit_mut_for_in_stmt(&mut self, node: &mut ForInStmt) { let blockifyed = self.blockify_for_stmt_body(&mut node.body); let lexical_var = if let ForHead::VarDecl(decl) = &node.left { @@ -544,6 +537,14 @@ impl VisitMut for BlockScoping { self.visit_mut_stmt_like(n); } + fn visit_mut_switch_case(&mut self, n: &mut SwitchCase) { + let old_vars = self.vars.take(); + + n.visit_mut_children_with(self); + + self.vars = old_vars; + } + fn visit_mut_var_decl(&mut self, var: &mut VarDecl) { let old = self.var_decl_kind; self.var_decl_kind = var.kind; @@ -569,6 +570,13 @@ impl VisitMut for BlockScoping { } } } + + fn visit_mut_while_stmt(&mut self, node: &mut WhileStmt) { + self.visit_mut_with_scope(ScopeKind::new_loop(), &mut node.body); + + node.test.visit_mut_with(self); + self.handle_capture_of_vars(&mut node.body); + } } impl BlockScoping { diff --git a/crates/swc_ecma_transforms_compat/src/es2015/classes/mod.rs b/crates/swc_ecma_transforms_compat/src/es2015/classes/mod.rs index dc27b327ce..e9660266cc 100644 --- a/crates/swc_ecma_transforms_compat/src/es2015/classes/mod.rs +++ b/crates/swc_ecma_transforms_compat/src/es2015/classes/mod.rs @@ -154,7 +154,7 @@ where .into()], src: None, type_only: false, - asserts: None, + with: None, }, )) { Ok(t) => t, diff --git a/crates/swc_ecma_transforms_compat/src/es2015/for_of.rs b/crates/swc_ecma_transforms_compat/src/es2015/for_of.rs index b509443934..d7ddb6be38 100644 --- a/crates/swc_ecma_transforms_compat/src/es2015/for_of.rs +++ b/crates/swc_ecma_transforms_compat/src/es2015/for_of.rs @@ -670,7 +670,7 @@ impl VisitMut for ForOf { *s = self.fold_for_stmt(Some(label.clone()), stmt.take()); } _ => { - body.visit_mut_children_with(self); + body.visit_mut_with(self); } } } diff --git a/crates/swc_ecma_transforms_compat/src/es2015/generator.rs b/crates/swc_ecma_transforms_compat/src/es2015/generator.rs index a6da0916a0..a409203a50 100644 --- a/crates/swc_ecma_transforms_compat/src/es2015/generator.rs +++ b/crates/swc_ecma_transforms_compat/src/es2015/generator.rs @@ -572,72 +572,70 @@ impl VisitMut for Generator { e.visit_mut_children_with(self); } - Expr::Assign(node) => { - if contains_yield(&node.right) { - match node.left.as_expr_mut() { - Some(Expr::Member(left)) => { - match &mut left.prop { - MemberProp::Ident(..) | MemberProp::PrivateName(..) => { - // a.b = yield; - // - // [intermediate] - // .local _a - // _a = a; - // .yield resumeLabel - // .mark resumeLabel - // _a.b = %sent%; - - left.obj.visit_mut_with(self); - let obj = self.cache_expression(left.obj.take()); - - left.obj = Box::new(Expr::Ident(obj)); - } - MemberProp::Computed(prop) => { - // [source] - // a[b] = yield; - // - // [intermediate] - // .local _a, _b - // _a = a; - // _b = b; - // .yield resumeLabel - // .mark resumeLabel - // _a[_b] = %sent%; - let prop_span = prop.span; - - left.obj.visit_mut_with(self); - let obj = self.cache_expression(left.obj.take()); - - prop.visit_mut_with(self); - let prop = self.cache_expression(prop.expr.take()); - - left.obj = Box::new(Expr::Ident(obj)); - left.prop = MemberProp::Computed(ComputedPropName { - span: prop_span, - expr: Box::new(Expr::Ident(prop)), - }); - } + Expr::Assign(node) if contains_yield(&node.right) => { + match node.left.as_expr_mut() { + Some(Expr::Member(left)) => { + match &mut left.prop { + MemberProp::Ident(..) | MemberProp::PrivateName(..) => { + // a.b = yield; + // + // [intermediate] + // .local _a + // _a = a; + // .yield resumeLabel + // .mark resumeLabel + // _a.b = %sent%; + + left.obj.visit_mut_with(self); + let obj = self.cache_expression(left.obj.take()); + + left.obj = Box::new(Expr::Ident(obj)); + } + MemberProp::Computed(prop) => { + // [source] + // a[b] = yield; + // + // [intermediate] + // .local _a, _b + // _a = a; + // _b = b; + // .yield resumeLabel + // .mark resumeLabel + // _a[_b] = %sent%; + let prop_span = prop.span; + + left.obj.visit_mut_with(self); + let obj = self.cache_expression(left.obj.take()); + + prop.visit_mut_with(self); + let prop = self.cache_expression(prop.expr.take()); + + left.obj = Box::new(Expr::Ident(obj)); + left.prop = MemberProp::Computed(ComputedPropName { + span: prop_span, + expr: Box::new(Expr::Ident(prop)), + }); } - // [source] - } - _ => { - node.left.visit_mut_with(self); } + // [source] + } + _ => { + node.left.visit_mut_with(self); } - if node.op != op!("=") { - let left_of_right = self.cache_expression(node.left.take().expect_expr()); + } + if node.op != op!("=") { + let left_of_right = self.cache_expression(node.left.take().expect_expr()); - node.right.visit_mut_with(self); + node.right.visit_mut_with(self); - *e = Expr::Assign(AssignExpr { - span: node.right.span(), - op: node.op, - left: left_of_right.into(), - right: node.right.take(), - }); - } else { - node.right.visit_mut_with(self); - } + *e = Expr::Assign(AssignExpr { + span: node.right.span(), + op: node.op, + left: left_of_right.into(), + right: node.right.take(), + }); + } else { + node.right.visit_mut_with(self); } } diff --git a/crates/swc_ecma_transforms_compat/src/es2018/object_rest.rs b/crates/swc_ecma_transforms_compat/src/es2018/object_rest.rs index 269d987bd6..39e6fc1253 100644 --- a/crates/swc_ecma_transforms_compat/src/es2018/object_rest.rs +++ b/crates/swc_ecma_transforms_compat/src/es2018/object_rest.rs @@ -291,7 +291,7 @@ impl VisitMut for ObjectRest { specifiers, src: None, type_only: false, - asserts: None, + with: None, }; var_decl.visit_mut_with(self); diff --git a/crates/swc_ecma_transforms_compat/src/es2020/export_namespace_from.rs b/crates/swc_ecma_transforms_compat/src/es2020/export_namespace_from.rs index 3ca62e97c3..98ea8e3f57 100644 --- a/crates/swc_ecma_transforms_compat/src/es2020/export_namespace_from.rs +++ b/crates/swc_ecma_transforms_compat/src/es2020/export_namespace_from.rs @@ -40,7 +40,7 @@ impl VisitMut for ExportNamespaceFrom { specifiers, src: Some(src), type_only: false, - asserts, + with, })) if specifiers.iter().any(|s| s.is_namespace()) => { let mut origin_specifiers = vec![]; @@ -79,7 +79,7 @@ impl VisitMut for ExportNamespaceFrom { specifiers: import_specifiers, src: src.clone(), type_only: false, - asserts: asserts.clone(), + with: with.clone(), }))); stmts.push(ModuleItem::ModuleDecl(ModuleDecl::ExportNamed( @@ -88,7 +88,7 @@ impl VisitMut for ExportNamespaceFrom { specifiers: export_specifiers, src: None, type_only: false, - asserts: None, + with: None, }, ))); @@ -99,7 +99,7 @@ impl VisitMut for ExportNamespaceFrom { specifiers: origin_specifiers, src: Some(src), type_only: false, - asserts, + with, }, ))); } diff --git a/crates/swc_ecma_transforms_compat/src/es2020/mod.rs b/crates/swc_ecma_transforms_compat/src/es2020/mod.rs index 39d5c67409..c116b3c6de 100644 --- a/crates/swc_ecma_transforms_compat/src/es2020/mod.rs +++ b/crates/swc_ecma_transforms_compat/src/es2020/mod.rs @@ -1,5 +1,5 @@ use serde::Deserialize; -use swc_common::chain; +use swc_common::{chain, Mark}; use swc_ecma_visit::Fold; pub use self::{ @@ -11,10 +11,10 @@ mod export_namespace_from; pub mod nullish_coalescing; pub mod optional_chaining; -pub fn es2020(config: Config) -> impl Fold { +pub fn es2020(config: Config, unresolved_mark: Mark) -> impl Fold { chain!( nullish_coalescing(config.nullish_coalescing), - optional_chaining(config.optional_chaining), + optional_chaining(config.optional_chaining, unresolved_mark), export_namespace_from(), ) } diff --git a/crates/swc_ecma_transforms_compat/src/es2020/optional_chaining.rs b/crates/swc_ecma_transforms_compat/src/es2020/optional_chaining.rs index 1ce54d2184..034907e4a2 100644 --- a/crates/swc_ecma_transforms_compat/src/es2020/optional_chaining.rs +++ b/crates/swc_ecma_transforms_compat/src/es2020/optional_chaining.rs @@ -1,16 +1,17 @@ use std::mem; use serde::Deserialize; -use swc_common::{util::take::Take, DUMMY_SP}; +use swc_common::{util::take::Take, Mark, SyntaxContext, DUMMY_SP}; use swc_ecma_ast::*; use swc_ecma_utils::{ alias_ident_for, prepend_stmt, quote_ident, undefined, ExprFactory, StmtLike, }; use swc_ecma_visit::{as_folder, noop_visit_mut_type, Fold, VisitMut, VisitMutWith}; -pub fn optional_chaining(c: Config) -> impl Fold + VisitMut { +pub fn optional_chaining(c: Config, unresolved_mark: Mark) -> impl Fold + VisitMut { as_folder(OptChaining { c, + unresolved: SyntaxContext::empty().apply_mark(unresolved_mark), ..Default::default() }) } @@ -18,6 +19,7 @@ pub fn optional_chaining(c: Config) -> impl Fold + VisitMut { #[derive(Default)] struct OptChaining { vars: Vec, + unresolved: SyntaxContext, c: Config, } @@ -63,7 +65,7 @@ impl VisitMut for OptChaining { // foo?.bar -> foo == null ? void 0 : foo.bar Expr::OptChain(v) => { let data = self.gather(v.take(), vec![]); - *e = self.construct(data, false, self.c.no_document_all); + *e = self.construct(data, false); } Expr::Unary(UnaryExpr { @@ -75,7 +77,7 @@ impl VisitMut for OptChaining { // delete foo?.bar -> foo == null ? true : delete foo.bar Expr::OptChain(v) => { let data = self.gather(v.take(), vec![]); - *e = self.construct(data, true, self.c.no_document_all); + *e = self.construct(data, true); } _ => e.visit_mut_children_with(self), } @@ -145,12 +147,27 @@ impl VisitMut for OptChaining { } } +#[derive(Debug, Clone)] +enum Memo { + Cache(Ident), + Raw(Box), +} + +impl Memo { + fn into_expr(self) -> Expr { + match self { + Memo::Cache(i) => Expr::Ident(i), + Memo::Raw(e) => *e, + } + } +} + #[derive(Debug)] enum Gathering { Call(CallExpr), Member(MemberExpr), - OptCall(CallExpr, Ident), - OptMember(MemberExpr, Ident), + OptCall(CallExpr, Memo), + OptMember(MemberExpr, Memo), } impl OptChaining { @@ -181,7 +198,7 @@ impl OptChaining { next = m.obj.take(); m.prop.visit_mut_with(self); chain.push(if optional { - Gathering::OptMember(m.take(), self.memoize(&next)) + Gathering::OptMember(m.take(), self.memoize(&next, false)) } else { Gathering::Member(m.take()) }); @@ -192,7 +209,7 @@ impl OptChaining { c.args.visit_mut_with(self); // I don't know why c is an OptCall instead of a CallExpr. chain.push(if optional { - Gathering::OptCall(c.take().into(), self.memoize(&next)) + Gathering::OptCall(c.take().into(), self.memoize(&next, true)) } else { Gathering::Call(c.take().into()) }); @@ -213,12 +230,7 @@ impl OptChaining { /// Constructs a rightward nested conditional expression out of our /// flattened chain. - fn construct( - &mut self, - data: (Expr, usize, Vec), - is_delete: bool, - no_document_all: bool, - ) -> Expr { + fn construct(&mut self, data: (Expr, usize, Vec), is_delete: bool) -> Expr { let (mut current, count, chain) = data; // Stores partially constructed CondExprs for us to assemble later on. @@ -260,16 +272,22 @@ impl OptChaining { Expr::Member(m) => { call = true; let this = ctx.unwrap_or_else(|| { - let this = self.memoize(&m.obj); - m.obj = Box::new(Expr::Assign(AssignExpr { - span: DUMMY_SP, - op: op!("="), - left: this.clone().into(), - right: m.obj.take(), - })); - this + let this = self.memoize(&m.obj, true); + + match &this { + Memo::Cache(i) => { + m.obj = Box::new(Expr::Assign(AssignExpr { + span: DUMMY_SP, + op: op!("="), + left: i.clone().into(), + right: m.obj.take(), + })); + this + } + Memo::Raw(_) => this, + } }); - c.args.insert(0, this.as_arg()); + c.args.insert(0, this.into_expr().as_arg()); } Expr::SuperProp(s) => { call = true; @@ -280,7 +298,7 @@ impl OptChaining { committed_cond.push(CondExpr { span: DUMMY_SP, - test: init_and_eq_null_or_undefined(&memo, current, no_document_all), + test: init_and_eq_null_or_undefined(&memo, current, self.c.no_document_all), cons: if is_delete { true.into() } else { @@ -289,9 +307,11 @@ impl OptChaining { alt: Take::dummy(), }); c.callee = if call { - memo.make_member(quote_ident!("call")).as_callee() + memo.into_expr() + .make_member(quote_ident!("call")) + .as_callee() } else { - memo.as_callee() + memo.into_expr().as_callee() }; ctx = None; Expr::Call(c) @@ -299,7 +319,7 @@ impl OptChaining { Gathering::OptMember(mut m, memo) => { committed_cond.push(CondExpr { span: DUMMY_SP, - test: init_and_eq_null_or_undefined(&memo, current, no_document_all), + test: init_and_eq_null_or_undefined(&memo, current, self.c.no_document_all), cons: if is_delete { true.into() } else { @@ -308,7 +328,7 @@ impl OptChaining { alt: Take::dummy(), }); ctx = Some(memo.clone()); - m.obj = memo.into(); + m.obj = memo.into_expr().into(); Expr::Member(m) } }; @@ -331,15 +351,41 @@ impl OptChaining { current } - fn memoize(&mut self, expr: &Expr) -> Ident { - let memo = alias_ident_for(expr, "_this"); - self.vars.push(VarDeclarator { - span: DUMMY_SP, - name: memo.clone().into(), - init: None, - definite: false, - }); - memo + fn should_memo(&self, expr: &Expr, is_call: bool) -> bool { + fn is_simple_member(e: &Expr) -> bool { + match e { + Expr::Ident(_) => true, + Expr::SuperProp(s) if !s.prop.is_computed() => true, + Expr::Member(m) if !m.prop.is_computed() => is_simple_member(&m.obj), + _ => false, + } + } + + match expr { + Expr::Ident(i) if i.span.ctxt != self.unresolved => false, + _ => { + if is_call && self.c.pure_getter { + !is_simple_member(expr) + } else { + true + } + } + } + } + + fn memoize(&mut self, expr: &Expr, is_call: bool) -> Memo { + if self.should_memo(expr, is_call) { + let memo = alias_ident_for(expr, "_this"); + self.vars.push(VarDeclarator { + span: DUMMY_SP, + name: memo.clone().into(), + init: None, + definite: false, + }); + Memo::Cache(memo) + } else { + Memo::Raw(Box::new(expr.to_owned())) + } } fn visit_mut_stmt_like(&mut self, stmts: &mut Vec) @@ -371,13 +417,16 @@ impl OptChaining { } } -fn init_and_eq_null_or_undefined(i: &Ident, init: Expr, no_document_all: bool) -> Box { - let lhs = Box::new(Expr::Assign(AssignExpr { - span: DUMMY_SP, - op: op!("="), - left: PatOrExpr::Pat(i.clone().into()), - right: Box::new(init), - })); +fn init_and_eq_null_or_undefined(i: &Memo, init: Expr, no_document_all: bool) -> Box { + let lhs = match i { + Memo::Cache(i) => Box::new(Expr::Assign(AssignExpr { + span: DUMMY_SP, + op: op!("="), + left: PatOrExpr::Pat(i.clone().into()), + right: Box::new(init), + })), + Memo::Raw(e) => e.to_owned(), + }; if no_document_all { return Box::new(Expr::Bin(BinExpr { @@ -395,9 +444,14 @@ fn init_and_eq_null_or_undefined(i: &Ident, init: Expr, no_document_all: bool) - right: Box::new(Expr::Lit(Lit::Null(Null { span: DUMMY_SP }))), })); + let left_expr = match i { + Memo::Cache(i) => Box::new(i.clone().into()), + Memo::Raw(e) => e.to_owned(), + }; + let void_cmp = Box::new(Expr::Bin(BinExpr { span: DUMMY_SP, - left: Box::new(Expr::Ident(i.clone())), + left: left_expr, op: op!("==="), right: undefined(DUMMY_SP), })); diff --git a/crates/swc_ecma_transforms_compat/src/es2022/class_properties/mod.rs b/crates/swc_ecma_transforms_compat/src/es2022/class_properties/mod.rs index 5ba50de0c4..c83ebc2316 100644 --- a/crates/swc_ecma_transforms_compat/src/es2022/class_properties/mod.rs +++ b/crates/swc_ecma_transforms_compat/src/es2022/class_properties/mod.rs @@ -365,7 +365,7 @@ impl ClassProperties { .into()], src: None, type_only: false, - asserts: None, + with: None, }, )) { Ok(t) => t, diff --git a/crates/swc_ecma_transforms_compat/src/es2022/static_blocks.rs b/crates/swc_ecma_transforms_compat/src/es2022/static_blocks.rs index 5a35cbbf4f..ef47681494 100644 --- a/crates/swc_ecma_transforms_compat/src/es2022/static_blocks.rs +++ b/crates/swc_ecma_transforms_compat/src/es2022/static_blocks.rs @@ -17,11 +17,40 @@ pub fn static_blocks(mark: Mark) -> impl Fold + VisitMut { impl ClassStaticBlock { fn visit_mut_static_block( &mut self, - static_block: StaticBlock, + mut static_block: StaticBlock, private_id: JsWord, ) -> PrivateProp { + let mut stmts = static_block.body.stmts.take(); + let span = static_block.span; + + // We special-case the single expression case to avoid the iife, since it's + // common. + let value = if stmts.len() == 1 && stmts[0].is_expr() { + stmts[0].take().expr().map(|expr_stmt| expr_stmt.expr) + } else { + static_block.body.stmts = stmts; + + let expr = Expr::Call(CallExpr { + span: DUMMY_SP, + callee: ArrowExpr { + span: DUMMY_SP, + params: Vec::new(), + is_async: false, + is_generator: false, + type_params: None, + return_type: None, + body: Box::new(BlockStmtOrExpr::BlockStmt(static_block.body)), + } + .as_callee(), + args: Vec::new(), + type_args: None, + }); + + Some(Box::new(expr)) + }; + PrivateProp { - span: DUMMY_SP.apply_mark(self.mark), + span: span.apply_mark(self.mark), is_static: true, is_optional: false, is_override: false, @@ -37,21 +66,7 @@ impl ClassStaticBlock { optional: false, }, }, - value: Some(Box::new(Expr::Call(CallExpr { - span: DUMMY_SP, - callee: ArrowExpr { - span: DUMMY_SP, - params: Vec::new(), - is_async: false, - is_generator: false, - type_params: None, - return_type: None, - body: Box::new(BlockStmtOrExpr::BlockStmt(static_block.body)), - } - .as_callee(), - args: Vec::new(), - type_args: None, - }))), + value, definite: false, } } diff --git a/crates/swc_ecma_transforms_compat/src/es3/reserved_word.rs b/crates/swc_ecma_transforms_compat/src/es3/reserved_word.rs index 7df077e4b3..c1f8dbd679 100644 --- a/crates/swc_ecma_transforms_compat/src/es3/reserved_word.rs +++ b/crates/swc_ecma_transforms_compat/src/es3/reserved_word.rs @@ -72,7 +72,7 @@ impl VisitMut for ReservedWord { specifiers: extra_exports, src: None, type_only: false, - asserts: None, + with: None, } .into(), ); diff --git a/crates/swc_ecma_transforms_compat/tests/es2015_generator.rs b/crates/swc_ecma_transforms_compat/tests/es2015_generator.rs index 1b7a8f5499..4a29cfb36d 100644 --- a/crates/swc_ecma_transforms_compat/tests/es2015_generator.rs +++ b/crates/swc_ecma_transforms_compat/tests/es2015_generator.rs @@ -1937,3 +1937,47 @@ test_exec!( await res; " ); + +test!( + Syntax::default(), + |_| { + let mark = Mark::fresh(Mark::root()); + es2015::(mark, None, Default::default()) + }, + issue_7809, + r#" + function a(fn) { + return _a.apply(this, arguments); + } + function _a() { + _a = _async_to_generator(function*(fn) { + (yield fn()).a = 1; + }); + return _a.apply(this, arguments); + } + "#, + r#" + function a(fn) { + return _a.apply(this, arguments); + } + function _a() { + _a = _async_to_generator(function(fn) { + return _ts_generator(this, function(_state) { + switch(_state.label){ + case 0: + return [ + 4, + fn() + ]; + case 1: + _state.sent().a = 1; + return [ + 2 + ]; + } + }); + }); + return _a.apply(this, arguments); + } + "# +); diff --git a/crates/swc_ecma_transforms_compat/tests/es2020_optional_chaining.rs b/crates/swc_ecma_transforms_compat/tests/es2020_optional_chaining.rs index 63b6d96ca4..5f8161c5d8 100644 --- a/crates/swc_ecma_transforms_compat/tests/es2020_optional_chaining.rs +++ b/crates/swc_ecma_transforms_compat/tests/es2020_optional_chaining.rs @@ -1,12 +1,19 @@ use std::{fs::read_to_string, path::PathBuf}; +use swc_common::{chain, Mark}; use swc_ecma_parser::Syntax; +use swc_ecma_transforms_base::resolver; use swc_ecma_transforms_compat::es2020::{optional_chaining, optional_chaining::Config}; use swc_ecma_transforms_testing::{compare_stdout, test, test_exec, test_fixture}; use swc_ecma_visit::Fold; fn tr(c: Config) -> impl Fold { - optional_chaining(c) + let unresolved_mark = Mark::new(); + let top_level_mark = Mark::new(); + chain!( + resolver(unresolved_mark, top_level_mark, false), + optional_chaining(c, unresolved_mark) + ) } fn syntax() -> Syntax { @@ -264,10 +271,18 @@ fn exec(input: PathBuf) { compare_stdout( Default::default(), |_| { - optional_chaining(Config { - no_document_all: true, - ..Default::default() - }) + let unresolved_mark = Mark::new(); + let top_level_mark = Mark::new(); + chain!( + resolver(unresolved_mark, top_level_mark, false), + optional_chaining( + Config { + no_document_all: true, + ..Default::default() + }, + Mark::new(), + ) + ) }, &src, ); @@ -279,7 +294,14 @@ fn fixture(input: PathBuf) { test_fixture( Default::default(), - &|_| optional_chaining(Default::default()), + &|_| { + let unresolved_mark = Mark::new(); + let top_level_mark = Mark::new(); + chain!( + resolver(unresolved_mark, top_level_mark, false), + optional_chaining(Default::default(), unresolved_mark) + ) + }, &input, &output, Default::default(), @@ -293,10 +315,18 @@ fn fixture_loose(input: PathBuf) { test_fixture( Default::default(), &|_| { - optional_chaining(Config { - no_document_all: true, - pure_getter: true, - }) + let unresolved_mark = Mark::new(); + let top_level_mark = Mark::new(); + chain!( + resolver(unresolved_mark, top_level_mark, false), + optional_chaining( + Config { + no_document_all: true, + pure_getter: true, + }, + Mark::new(), + ) + ) }, &input, &output, diff --git a/crates/swc_ecma_transforms_macros/src/parallel.rs b/crates/swc_ecma_transforms_macros/src/parallel.rs index 006ee04400..2a0da6eddb 100644 --- a/crates/swc_ecma_transforms_macros/src/parallel.rs +++ b/crates/swc_ecma_transforms_macros/src/parallel.rs @@ -1,3 +1,5 @@ +#![allow(non_snake_case)] + use pmutil::q; use proc_macro2::{Span, TokenStream}; use syn::{Expr, Ident, ImplItem, ImplItemFn, ItemImpl, Meta, Type}; @@ -29,11 +31,9 @@ pub fn expand(attr: TokenStream, mut item: ItemImpl) -> ItemImpl { mode, "module_items", explode, - 100, - ))); - item.items.push(ImplItem::Fn(make_par_visit_method( - mode, "stmts", explode, 100, ))); + item.items + .push(ImplItem::Fn(make_par_visit_method(mode, "stmts", explode))); item } @@ -85,20 +85,15 @@ fn explode_hook_method_name(explode: bool, suffix: &str) -> Option { } } -fn make_par_visit_method(mode: Mode, suffix: &str, explode: bool, threshold: usize) -> ImplItemFn { +fn make_par_visit_method(mode: Mode, suffix: &str, explode: bool) -> ImplItemFn { let method_name = Ident::new(&format!("{}_{}", mode.prefix(), suffix), Span::call_site()); let hook = post_visit_hook(mode, suffix); let explode_method_name = explode_hook_method_name(explode, suffix); - let threshold = q!(Vars { threshold }, { - (swc_ecma_transforms_base::perf::cpu_count() * threshold) - }); - match (mode, explode_method_name) { (Mode::Fold, Some(explode_method_name)) => q!( Vars { NodeType: node_type(suffix), - threshold, method_name, hook, explode_method_name, @@ -109,63 +104,6 @@ fn make_par_visit_method(mode: Mode, suffix: &str, explode: bool, threshold: usi use swc_ecma_transforms_base::perf::{ParExplode, Parallel}; use swc_ecma_visit::FoldWith; - #[cfg(feature = "rayon")] - if nodes.len() >= threshold || option_env!("SWC_FORCE_CONCURRENT") == Some("1") - { - use rayon::prelude::*; - - let (visitor, mut nodes) = ::swc_common::GLOBALS.with(|globals| { - swc_ecma_transforms_base::helpers::HELPERS.with(|helpers| { - HANDLER.with(|handler| { - nodes - .into_par_iter() - .map(|node| { - ::swc_common::GLOBALS.set(&globals, || { - swc_ecma_transforms_base::helpers::HELPERS.set( - helpers, - || { - HANDLER.set(handler, || { - let mut visitor = - Parallel::create(&*self); - let node = node.fold_with(&mut visitor); - let mut nodes = Vec::with_capacity(4); - - ParExplode::explode_method_name( - &mut visitor, - &mut nodes, - ); - - nodes.push(node); - - (visitor, nodes) - }) - }, - ) - }) - }) - .reduce( - || (Parallel::create(&*self), vec![]), - |mut a, b| { - Parallel::merge(&mut a.0, b.0); - - a.1.extend(b.1); - - a - }, - ) - }) - }) - }); - - Parallel::merge(self, visitor); - - { - hook; - } - - return nodes; - } - let mut buf = Vec::with_capacity(nodes.len()); for node in nodes { @@ -189,7 +127,6 @@ fn make_par_visit_method(mode: Mode, suffix: &str, explode: bool, threshold: usi (Mode::Fold, None) => q!( Vars { NodeType: node_type(suffix), - threshold, method_name, hook, }, @@ -199,65 +136,6 @@ fn make_par_visit_method(mode: Mode, suffix: &str, explode: bool, threshold: usi use swc_ecma_transforms_base::perf::Parallel; use swc_ecma_visit::FoldWith; - #[cfg(feature = "rayon")] - if nodes.len() >= threshold || option_env!("SWC_FORCE_CONCURRENT") == Some("1") - { - use rayon::prelude::*; - - let (visitor, mut nodes) = ::swc_common::GLOBALS.with(|globals| { - swc_ecma_transforms_base::helpers::HELPERS.with(|helpers| { - HANDLER.with(|handler| { - nodes - .into_par_iter() - .map(|node| { - ::swc_common::GLOBALS.set(&globals, || { - swc_ecma_transforms_base::helpers::HELPERS.set( - helpers, - || { - HANDLER.set(handler, || { - let mut visitor = - Parallel::create(&*self); - let node = node.fold_with(&mut visitor); - - (visitor, node) - }) - }, - ) - }) - }) - .fold( - || (Parallel::create(&*self), vec![]), - |mut a, b| { - Parallel::merge(&mut a.0, b.0); - - a.1.push(b.1); - - a - }, - ) - .reduce( - || (Parallel::create(&*self), vec![]), - |mut a, b| { - Parallel::merge(&mut a.0, b.0); - - a.1.extend(b.1); - - a - }, - ) - }) - }) - }); - - Parallel::merge(self, visitor); - - { - hook; - } - - return nodes; - } - let mut nodes = nodes.fold_children_with(self); { hook; @@ -272,7 +150,6 @@ fn make_par_visit_method(mode: Mode, suffix: &str, explode: bool, threshold: usi (Mode::VisitMut, Some(explode_method_name)) => q!( Vars { NodeType: node_type(suffix), - threshold, method_name, hook, explode_method_name @@ -285,66 +162,6 @@ fn make_par_visit_method(mode: Mode, suffix: &str, explode: bool, threshold: usi use swc_ecma_transforms_base::perf::{ParExplode, Parallel}; use swc_ecma_visit::VisitMutWith; - #[cfg(feature = "rayon")] - if nodes.len() >= threshold || option_env!("SWC_FORCE_CONCURRENT") == Some("1") - { - ::swc_common::GLOBALS.with(|globals| { - swc_ecma_transforms_base::helpers::HELPERS.with(|helpers| { - HANDLER.with(|handler| { - use rayon::prelude::*; - - let (visitor, new_nodes) = take(nodes) - .into_par_iter() - .map(|mut node| { - ::swc_common::GLOBALS.set(&globals, || { - swc_ecma_transforms_base::helpers::HELPERS.set( - helpers, - || { - HANDLER.set(handler, || { - let mut visitor = - Parallel::create(&*self); - node.visit_mut_with(&mut visitor); - - let mut nodes = Vec::with_capacity(4); - - ParExplode::explode_method_name( - &mut visitor, - &mut nodes, - ); - - nodes.push(node); - - (visitor, nodes) - }) - }, - ) - }) - }) - .reduce( - || (Parallel::create(&*self), vec![]), - |mut a, b| { - Parallel::merge(&mut a.0, b.0); - - a.1.extend(b.1); - - a - }, - ); - - Parallel::merge(self, visitor); - - { - hook; - } - - *nodes = new_nodes; - }) - }) - }); - - return; - } - let mut buf = Vec::with_capacity(nodes.len()); for mut node in take(nodes) { @@ -367,7 +184,6 @@ fn make_par_visit_method(mode: Mode, suffix: &str, explode: bool, threshold: usi (Mode::VisitMut, None) => q!( Vars { NodeType: node_type(suffix), - threshold, method_name, hook, }, @@ -377,53 +193,6 @@ fn make_par_visit_method(mode: Mode, suffix: &str, explode: bool, threshold: usi use swc_ecma_transforms_base::perf::Parallel; use swc_ecma_visit::VisitMutWith; - #[cfg(feature = "rayon")] - if nodes.len() >= threshold || option_env!("SWC_FORCE_CONCURRENT") == Some("1") - { - ::swc_common::GLOBALS.with(|globals| { - swc_ecma_transforms_base::helpers::HELPERS.with(|helpers| { - HANDLER.with(|handler| { - use rayon::prelude::*; - - let visitor = nodes - .into_par_iter() - .map(|node| { - ::swc_common::GLOBALS.set(&globals, || { - swc_ecma_transforms_base::helpers::HELPERS.set( - helpers, - || { - HANDLER.set(handler, || { - let mut visitor = - Parallel::create(&*self); - node.visit_mut_with(&mut visitor); - - visitor - }) - }, - ) - }) - }) - .reduce( - || Parallel::create(&*self), - |mut a, b| { - Parallel::merge(&mut a, b); - - a - }, - ); - - Parallel::merge(self, visitor); - - { - hook; - } - }) - }) - }); - - return; - } - nodes.visit_mut_children_with(self); { hook; diff --git a/crates/swc_ecma_transforms_module/src/lib.rs b/crates/swc_ecma_transforms_module/src/lib.rs index 6c73f690dd..bfc8b3869f 100644 --- a/crates/swc_ecma_transforms_module/src/lib.rs +++ b/crates/swc_ecma_transforms_module/src/lib.rs @@ -3,6 +3,8 @@ #![allow(clippy::needless_lifetimes)] #![allow(clippy::vec_box)] +use serde::{Deserialize, Serialize}; + pub use self::{amd::amd, common_js::common_js, system_js::system_js, umd::umd}; #[macro_use] @@ -16,3 +18,10 @@ pub mod path; pub mod rewriter; pub mod system_js; pub mod umd; + +#[derive(Debug, Default, Clone, Serialize, Deserialize)] +#[serde(deny_unknown_fields, rename_all = "camelCase")] +pub struct EsModuleConfig { + #[serde(default)] + pub resolve_fully: bool, +} diff --git a/crates/swc_ecma_transforms_module/src/path.rs b/crates/swc_ecma_transforms_module/src/path.rs index 7f10d7e527..7c33109ef7 100644 --- a/crates/swc_ecma_transforms_module/src/path.rs +++ b/crates/swc_ecma_transforms_module/src/path.rs @@ -14,7 +14,7 @@ use swc_common::{FileName, Mark, Span, DUMMY_SP}; use swc_ecma_ast::*; use swc_ecma_loader::resolve::Resolve; use swc_ecma_utils::{quote_ident, ExprFactory}; -use tracing::{debug, trace, warn, Level}; +use tracing::{debug, info, warn, Level}; pub(crate) enum Resolver { Real { @@ -86,21 +86,38 @@ where R: Resolve, { resolver: R, - base_dir: Option, + config: Config, +} + +#[derive(Debug, Clone, Default)] +pub struct Config { + pub base_dir: Option, + pub resolve_fully: bool, } impl NodeImportResolver where R: Resolve, { - #[deprecated(note = "Use `with_base_dir`")] + #[deprecated(note = "Use `with_config`")] pub fn new(resolver: R) -> Self { - Self::with_base_dir(resolver, None) + Self::with_config(resolver, Default::default()) } + #[deprecated(note = "Use `with_config`")] pub fn with_base_dir(resolver: R, base_dir: Option) -> Self { + Self::with_config( + resolver, + Config { + base_dir, + ..Default::default() + }, + ) + } + + pub fn with_config(resolver: R, config: Config) -> Self { #[cfg(not(target_arch = "wasm32"))] - if let Some(base_dir) = &base_dir { + if let Some(base_dir) = &config.base_dir { assert!( base_dir.is_absolute(), "base_dir(`{}`) must be absolute. Please ensure that `jsc.baseUrl` is specified \ @@ -115,43 +132,69 @@ where ); } - Self { resolver, base_dir } + Self { resolver, config } } } -impl ImportResolver for NodeImportResolver +impl NodeImportResolver where R: Resolve, { - fn resolve_import(&self, base: &FileName, module_specifier: &str) -> Result { - fn to_specifier(target_path: &str, orig_ext: Option<&str>) -> JsWord { - let mut p = PathBuf::from(target_path); + fn to_specifier(&self, mut target_path: PathBuf, orig_filename: Option<&str>) -> JsWord { + debug!( + "Creating a specifier for `{}` with original filename `{:?}`", + target_path.display(), + orig_filename + ); - if cfg!(debug_assertions) { - trace!("to_specifier({target_path}): orig_ext={:?}", orig_ext); - } + if let Some(orig_filename) = orig_filename { + let is_resolved_as_index = if let Some(stem) = target_path.file_stem() { + stem == "index" + } else { + false + }; - if let Some(orig_ext) = orig_ext { - let use_orig = if let Some(ext) = p.extension() { - ext == "ts" || ext == "tsx" + let is_resolved_as_ts = if let Some(ext) = target_path.extension() { + ext == "ts" || ext == "tsx" + } else { + false + }; + + let is_exact = if let Some(filename) = target_path.file_name() { + filename == orig_filename + } else { + false + }; + + if !is_resolved_as_index && !is_exact { + target_path.set_file_name(orig_filename); + } else if is_resolved_as_ts && is_exact { + if let Some(ext) = Path::new(orig_filename).extension() { + target_path.set_extension(ext); } else { - false - }; - - if use_orig { - if matches!(orig_ext, "js" | "mjs" | "cjs" | "jsx") { - p.set_extension(orig_ext); - } else { - p.set_extension(""); - } + target_path.set_extension("js"); + } + } else if self.config.resolve_fully && is_resolved_as_ts { + target_path.set_extension("js"); + } else if is_resolved_as_ts && is_resolved_as_index { + if orig_filename == "index" { + target_path.set_extension(""); + } else { + target_path.pop(); } - } else { - p.set_extension(""); } + } else { + target_path.set_extension(""); + } - p.display().to_string().into() + if cfg!(target_os = "windows") { + target_path.display().to_string().replace('\\', "/").into() + } else { + target_path.display().to_string().into() } + } + fn try_resolve_import(&self, base: &FileName, module_specifier: &str) -> Result { let _tracing = if cfg!(debug_assertions) { Some( tracing::span!( @@ -166,17 +209,7 @@ where None }; - if cfg!(debug_assertions) { - debug!("invoking resolver"); - } - - let orig_ext = module_specifier.split('/').last().and_then(|s| { - if s.contains('.') { - s.split('.').last() - } else { - None - } - }); + let orig_filename = module_specifier.split('/').last(); let target = self.resolver.resolve(base, module_specifier); let target = match target { @@ -187,9 +220,18 @@ where } }; + info!("Resolved to {}", target); + let mut target = match target { - FileName::Real(v) => v, - FileName::Custom(s) => return Ok(to_specifier(&s, orig_ext)), + FileName::Real(v) => { + // @nestjs/common should be preserved as a whole + if v.starts_with(".") || v.starts_with("..") || v.is_absolute() { + v + } else { + return Ok(self.to_specifier(v, orig_filename)); + } + } + FileName::Custom(s) => return Ok(self.to_specifier(s.into(), orig_filename)), _ => { unreachable!( "Node path provider does not support using `{:?}` as a target file name", @@ -215,10 +257,16 @@ where }; if base.is_absolute() != target.is_absolute() { - base = Cow::Owned(absolute_path(self.base_dir.as_deref(), &base)?); - target = absolute_path(self.base_dir.as_deref(), &target)?; + base = Cow::Owned(absolute_path(self.config.base_dir.as_deref(), &base)?); + target = absolute_path(self.config.base_dir.as_deref(), &target)?; } + debug!( + "Comparing values (after normalizing absoluteness)\nbase={}\ntarget={}", + base.display(), + target.display() + ); + let rel_path = diff_paths( &target, match base.parent() { @@ -229,9 +277,11 @@ where let rel_path = match rel_path { Some(v) => v, - None => return Ok(to_specifier(&target.display().to_string(), orig_ext)), + None => return Ok(self.to_specifier(target, orig_filename)), }; + debug!("Relative path: {}", rel_path.display()); + { // Check for `node_modules`. @@ -256,11 +306,21 @@ where } else { Cow::Owned(format!("./{}", s)) }; - if cfg!(target_os = "windows") { - Ok(to_specifier(&s.replace('\\', "/"), orig_ext)) - } else { - Ok(to_specifier(&s, orig_ext)) - } + + Ok(self.to_specifier(s.into_owned().into(), orig_filename)) + } +} + +impl ImportResolver for NodeImportResolver +where + R: Resolve, +{ + fn resolve_import(&self, base: &FileName, module_specifier: &str) -> Result { + self.try_resolve_import(base, module_specifier) + .or_else(|err| { + warn!("Failed to resolve import: {}", err); + Ok(module_specifier.into()) + }) } } diff --git a/crates/swc_ecma_transforms_module/src/system_js.rs b/crates/swc_ecma_transforms_module/src/system_js.rs index 520eb06b7d..758e8c524e 100644 --- a/crates/swc_ecma_transforms_module/src/system_js.rs +++ b/crates/swc_ecma_transforms_module/src/system_js.rs @@ -17,6 +17,9 @@ use crate::{ pub struct Config { #[serde(default)] pub allow_top_level_this: bool, + + #[serde(default)] + pub resolve_fully: bool, } struct SystemJs { diff --git a/crates/swc_ecma_transforms_module/src/umd/config.rs b/crates/swc_ecma_transforms_module/src/umd/config.rs index 3fb840eef0..1564b7283b 100644 --- a/crates/swc_ecma_transforms_module/src/umd/config.rs +++ b/crates/swc_ecma_transforms_module/src/umd/config.rs @@ -29,8 +29,10 @@ impl Config { .into_iter() .map(|(k, v)| { let parse = |s| { - let fm = cm - .new_source_file(FileName::Custom(format!("", s)), s); + let fm = cm.new_source_file( + FileName::Internal(format!("", s)), + s, + ); parse_file_as_expr( &fm, diff --git a/crates/swc_ecma_transforms_module/src/util.rs b/crates/swc_ecma_transforms_module/src/util.rs index ee607d1b95..823f9a2ef0 100644 --- a/crates/swc_ecma_transforms_module/src/util.rs +++ b/crates/swc_ecma_transforms_module/src/util.rs @@ -38,6 +38,9 @@ pub struct Config { pub ignore_dynamic: bool, #[serde(default)] pub preserve_import_meta: bool, + + #[serde(default)] + pub resolve_fully: bool, } impl Default for Config { @@ -52,6 +55,7 @@ impl Default for Config { no_interop: false, ignore_dynamic: false, preserve_import_meta: false, + resolve_fully: false, } } } diff --git a/crates/swc_ecma_transforms_module/tests/path_node.rs b/crates/swc_ecma_transforms_module/tests/path_node.rs index 6ce2a4388f..32c80dc58e 100644 --- a/crates/swc_ecma_transforms_module/tests/path_node.rs +++ b/crates/swc_ecma_transforms_module/tests/path_node.rs @@ -84,24 +84,23 @@ fn issue_4730() { type JscPathsProvider = NodeImportResolver>; -fn paths_resolver( - base_url: impl AsRef, - rules: Vec<(String, Vec)>, -) -> JscPathsProvider { - let base_url = base_url - .as_ref() +fn paths_resolver(base_dir: &Path, rules: Vec<(String, Vec)>) -> JscPathsProvider { + let base_dir = base_dir .to_path_buf() .canonicalize() .expect("failed to canonicalize"); - dbg!(&base_url); + dbg!(&base_dir); - NodeImportResolver::with_base_dir( + NodeImportResolver::with_config( TsConfigResolver::new( NodeModulesResolver::new(swc_ecma_loader::TargetEnv::Node, Default::default(), true), - base_url.clone(), + base_dir.clone(), rules, ), - Some(base_url), + swc_ecma_transforms_module::path::Config { + base_dir: Some(base_dir), + resolve_fully: false, + }, ) } @@ -134,7 +133,7 @@ fn fixture(input_dir: PathBuf) { let rules = config.paths.clone().into_iter().collect(); let resolver = - paths_resolver(config.base_url.clone().unwrap_or(input_dir.clone()), rules); + paths_resolver(&config.base_url.clone().unwrap_or(input_dir.clone()), rules); import_rewriter(FileName::Real(index_path.clone()), resolver) }, diff --git a/crates/swc_ecma_transforms_module/tests/system_js.rs b/crates/swc_ecma_transforms_module/tests/system_js.rs index 40aeae243f..998178ab3c 100644 --- a/crates/swc_ecma_transforms_module/tests/system_js.rs +++ b/crates/swc_ecma_transforms_module/tests/system_js.rs @@ -45,7 +45,8 @@ test!( |tester| tr( tester, Config { - allow_top_level_this: true + allow_top_level_this: true, + ..Default::default() } ), allow_top_level_this_true, @@ -67,7 +68,8 @@ test!( |tester| tr( tester, Config { - allow_top_level_this: false + allow_top_level_this: false, + ..Default::default() } ), iife, @@ -94,7 +96,8 @@ test!( |tester| tr( tester, Config { - allow_top_level_this: false + allow_top_level_this: false, + ..Default::default() } ), top_level_this_false_class, @@ -133,7 +136,8 @@ test!( |tester| tr( tester, Config { - allow_top_level_this: false + allow_top_level_this: false, + ..Default::default() } ), allow_top_level_this_false, diff --git a/crates/swc_ecma_transforms_optimization/src/const_modules.rs b/crates/swc_ecma_transforms_optimization/src/const_modules.rs index 5e578e07c1..ed71111e62 100644 --- a/crates/swc_ecma_transforms_optimization/src/const_modules.rs +++ b/crates/swc_ecma_transforms_optimization/src/const_modules.rs @@ -46,7 +46,10 @@ pub fn const_modules( fn parse_option(cm: &SourceMap, name: &str, src: String) -> Arc { static CACHE: Lazy, ARandomState>> = Lazy::new(DashMap::default); - let fm = cm.new_source_file(FileName::Custom(format!("", name)), src); + let fm = cm.new_source_file( + FileName::Internal(format!("", name)), + src, + ); if let Some(expr) = CACHE.get(&**fm.src) { return expr.clone(); } diff --git a/crates/swc_ecma_transforms_optimization/src/inline_globals.rs b/crates/swc_ecma_transforms_optimization/src/inline_globals.rs index 434ff49600..6e67a0afd4 100644 --- a/crates/swc_ecma_transforms_optimization/src/inline_globals.rs +++ b/crates/swc_ecma_transforms_optimization/src/inline_globals.rs @@ -4,9 +4,8 @@ use swc_common::{ sync::Lrc, }; use swc_ecma_ast::*; -use swc_ecma_transforms_base::perf::Parallel; -use swc_ecma_transforms_macros::parallel; -use swc_ecma_utils::{collect_decls, NodeIgnoringSpan}; +use swc_ecma_transforms_base::perf::{ParVisitMut, Parallel}; +use swc_ecma_utils::{collect_decls, parallel::cpu_count, NodeIgnoringSpan}; use swc_ecma_visit::{as_folder, noop_visit_mut_type, Fold, VisitMut, VisitMutWith}; /// The key will be compared using [EqIgnoreSpan::eq_ignore_span], and matched @@ -63,7 +62,6 @@ impl Parallel for InlineGlobals { fn merge(&mut self, _: Self) {} } -#[parallel] impl VisitMut for InlineGlobals { noop_visit_mut_type!(); @@ -208,6 +206,22 @@ impl VisitMut for InlineGlobals { script.visit_mut_children_with(self); } + + fn visit_mut_prop_or_spreads(&mut self, n: &mut Vec) { + self.visit_mut_par(cpu_count() * 8, n); + } + + fn visit_mut_expr_or_spreads(&mut self, n: &mut Vec) { + self.visit_mut_par(cpu_count() * 8, n); + } + + fn visit_mut_opt_vec_expr_or_spreads(&mut self, n: &mut Vec>) { + self.visit_mut_par(cpu_count() * 8, n); + } + + fn visit_mut_exprs(&mut self, n: &mut Vec>) { + self.visit_mut_par(cpu_count() * 8, n); + } } #[cfg(test)] diff --git a/crates/swc_ecma_transforms_optimization/src/json_parse.rs b/crates/swc_ecma_transforms_optimization/src/json_parse.rs index 05e15cd2bc..2306961b8f 100644 --- a/crates/swc_ecma_transforms_optimization/src/json_parse.rs +++ b/crates/swc_ecma_transforms_optimization/src/json_parse.rs @@ -4,7 +4,6 @@ use serde_json::Value; use swc_common::{util::take::Take, Spanned, DUMMY_SP}; use swc_ecma_ast::*; use swc_ecma_transforms_base::perf::Parallel; -use swc_ecma_transforms_macros::parallel; use swc_ecma_utils::{calc_literal_cost, member_expr, ExprFactory}; use swc_ecma_visit::{as_folder, noop_visit_mut_type, Fold, VisitMut, VisitMutWith}; @@ -54,7 +53,6 @@ impl Default for JsonParse { } } -#[parallel] impl VisitMut for JsonParse { noop_visit_mut_type!(); diff --git a/crates/swc_ecma_transforms_proposal/src/decorator_2022_03.rs b/crates/swc_ecma_transforms_proposal/src/decorator_2022_03.rs index b08bb7522c..9545436f92 100644 --- a/crates/swc_ecma_transforms_proposal/src/decorator_2022_03.rs +++ b/crates/swc_ecma_transforms_proposal/src/decorator_2022_03.rs @@ -1505,7 +1505,7 @@ impl VisitMut for Decorator202203 { specifiers: self.extra_exports.take(), src: None, type_only: false, - asserts: None, + with: None, }, ))); } diff --git a/crates/swc_ecma_transforms_proposal/src/decorators/legacy/mod.rs b/crates/swc_ecma_transforms_proposal/src/decorators/legacy/mod.rs index 1f19f83fa4..cabada213e 100644 --- a/crates/swc_ecma_transforms_proposal/src/decorators/legacy/mod.rs +++ b/crates/swc_ecma_transforms_proposal/src/decorators/legacy/mod.rs @@ -526,7 +526,7 @@ impl VisitMut for TscDecorator { specifiers: self.exports.take(), src: None, type_only: Default::default(), - asserts: Default::default(), + with: Default::default(), }, ))); } diff --git a/crates/swc_ecma_transforms_proposal/src/decorators/mod.rs b/crates/swc_ecma_transforms_proposal/src/decorators/mod.rs index 692c4d7021..026effac83 100644 --- a/crates/swc_ecma_transforms_proposal/src/decorators/mod.rs +++ b/crates/swc_ecma_transforms_proposal/src/decorators/mod.rs @@ -212,7 +212,7 @@ impl Fold for Decorators { .into()], src: None, type_only: false, - asserts: None, + with: None, }, ))); }}; diff --git a/crates/swc_ecma_transforms_proposal/src/explicit_resource_management.rs b/crates/swc_ecma_transforms_proposal/src/explicit_resource_management.rs index 344975aaeb..e860053898 100644 --- a/crates/swc_ecma_transforms_proposal/src/explicit_resource_management.rs +++ b/crates/swc_ecma_transforms_proposal/src/explicit_resource_management.rs @@ -113,7 +113,7 @@ impl ExplicitResourceManagement { })], src: None, type_only: Default::default(), - asserts: None, + with: None, })) .unwrap(), ); @@ -152,7 +152,7 @@ impl ExplicitResourceManagement { })], src: None, type_only: Default::default(), - asserts: None, + with: None, })) .unwrap(), ); diff --git a/crates/swc_ecma_transforms_proposal/src/export_default_from.rs b/crates/swc_ecma_transforms_proposal/src/export_default_from.rs index a310918f3f..6718a6d9ac 100644 --- a/crates/swc_ecma_transforms_proposal/src/export_default_from.rs +++ b/crates/swc_ecma_transforms_proposal/src/export_default_from.rs @@ -39,7 +39,7 @@ impl VisitMut for ExportDefaultFrom { specifiers, src: Some(src), type_only: false, - asserts, + with, })) if specifiers.iter().any(|s| s.is_default()) => { let mut origin_specifiers = vec![]; @@ -79,7 +79,7 @@ impl VisitMut for ExportDefaultFrom { specifiers: export_specifiers, src: Some(src.clone()), type_only: false, - asserts: None, + with: None, }, ))); @@ -90,7 +90,7 @@ impl VisitMut for ExportDefaultFrom { specifiers: origin_specifiers, src: Some(src), type_only: false, - asserts, + with, }, ))); } diff --git a/crates/swc_ecma_transforms_proposal/src/import_assertions.rs b/crates/swc_ecma_transforms_proposal/src/import_assertions.rs index 78aecc0575..5612a312f1 100644 --- a/crates/swc_ecma_transforms_proposal/src/import_assertions.rs +++ b/crates/swc_ecma_transforms_proposal/src/import_assertions.rs @@ -1,23 +1,27 @@ use swc_ecma_ast::{ExportAll, ImportDecl, NamedExport}; use swc_ecma_visit::{as_folder, noop_visit_mut_type, Fold, VisitMut}; -pub fn import_assertions() -> impl VisitMut + Fold { +#[deprecated(note = "Please use `import_assertions` instead")] +pub use self::import_attributes as import_assertions; + +pub fn import_attributes() -> impl VisitMut + Fold { as_folder(ImportAssertions) } + struct ImportAssertions; impl VisitMut for ImportAssertions { noop_visit_mut_type!(); fn visit_mut_import_decl(&mut self, n: &mut ImportDecl) { - n.asserts = None; + n.with = None; } fn visit_mut_export_all(&mut self, n: &mut ExportAll) { - n.asserts = None; + n.with = None; } fn visit_mut_named_export(&mut self, n: &mut NamedExport) { - n.asserts = None; + n.with = None; } } diff --git a/crates/swc_ecma_transforms_proposal/tests/explicit_resource_management.rs b/crates/swc_ecma_transforms_proposal/tests/explicit_resource_management.rs index e546b724f4..df568a7209 100644 --- a/crates/swc_ecma_transforms_proposal/tests/explicit_resource_management.rs +++ b/crates/swc_ecma_transforms_proposal/tests/explicit_resource_management.rs @@ -12,7 +12,7 @@ fn exec(input: PathBuf) { exec_tr( "explicit-resource-management", Syntax::Es(EsConfig { - using_decl: true, + explicit_resource_management: true, ..Default::default() }), |_| { @@ -42,7 +42,7 @@ fn run_fixture(input: PathBuf) { test_fixture( Syntax::Es(EsConfig { - using_decl: true, + explicit_resource_management: true, ..Default::default() }), &|_t| { diff --git a/crates/swc_ecma_transforms_proposal/tests/import_assertions.rs b/crates/swc_ecma_transforms_proposal/tests/import_assertions.rs index e4165c0b35..b4b79c980d 100644 --- a/crates/swc_ecma_transforms_proposal/tests/import_assertions.rs +++ b/crates/swc_ecma_transforms_proposal/tests/import_assertions.rs @@ -9,7 +9,7 @@ fn tr() -> impl Fold { fn syntax() -> Syntax { Syntax::Es(EsConfig { - import_assertions: true, + import_attributes: true, ..Default::default() }) } diff --git a/crates/swc_ecma_transforms_react/src/jsx/mod.rs b/crates/swc_ecma_transforms_react/src/jsx/mod.rs index 2fd57a788a..ea53f552cd 100644 --- a/crates/swc_ecma_transforms_react/src/jsx/mod.rs +++ b/crates/swc_ecma_transforms_react/src/jsx/mod.rs @@ -118,7 +118,7 @@ pub fn parse_expr_for_jsx( src: String, top_level_mark: Mark, ) -> Arc> { - let fm = cm.new_source_file(FileName::Custom(format!("", name)), src); + let fm = cm.new_source_file(FileName::Internal(format!("", name)), src); parse_file_as_expr( &fm, @@ -1059,7 +1059,7 @@ where } .into(), type_only: Default::default(), - asserts: Default::default(), + with: Default::default(), })), ) }); diff --git a/crates/swc_ecma_transforms_react/src/jsx/tests.rs b/crates/swc_ecma_transforms_react/src/jsx/tests.rs index 7ab15dd9f0..acf91be34d 100644 --- a/crates/swc_ecma_transforms_react/src/jsx/tests.rs +++ b/crates/swc_ecma_transforms_react/src/jsx/tests.rs @@ -1499,12 +1499,9 @@ fn test_script(src: &str, output: &Path, options: Options) { let mut buf = vec![]; let mut emitter = Emitter { - cfg: Config { - target: Default::default(), - ascii_only: true, - minify: false, - omit_last_semi: true, - }, + cfg: Config::default() + .with_ascii_only(true) + .with_omit_last_semi(true), cm: tester.cm.clone(), wr: Box::new(swc_ecma_codegen::text_writer::JsWriter::new( tester.cm.clone(), diff --git a/crates/swc_ecma_transforms_typescript/benches/compat.rs b/crates/swc_ecma_transforms_typescript/benches/compat.rs index 9b4e2ca9e7..705cb879ac 100644 --- a/crates/swc_ecma_transforms_typescript/benches/compat.rs +++ b/crates/swc_ecma_transforms_typescript/benches/compat.rs @@ -152,7 +152,7 @@ fn es2020_nullish_coalescing(b: &mut Bencher) { fn es2020_optional_chaining(b: &mut Bencher) { run(b, || { - swc_ecma_transforms_compat::es2020::optional_chaining(Default::default()) + swc_ecma_transforms_compat::es2020::optional_chaining(Default::default(), Mark::new()) }); } diff --git a/crates/swc_ecma_transforms_typescript/examples/ts_to_js.rs b/crates/swc_ecma_transforms_typescript/examples/ts_to_js.rs index 7edfc18cad..680d3ab0e0 100644 --- a/crates/swc_ecma_transforms_typescript/examples/ts_to_js.rs +++ b/crates/swc_ecma_transforms_typescript/examples/ts_to_js.rs @@ -80,10 +80,7 @@ fn main() { let mut buf = vec![]; { let mut emitter = Emitter { - cfg: swc_ecma_codegen::Config { - minify: false, - ..Default::default() - }, + cfg: swc_ecma_codegen::Config::default(), cm: cm.clone(), comments: Some(&comments), wr: JsWriter::new(cm.clone(), "\n", &mut buf, None), diff --git a/crates/swc_ecma_transforms_typescript/src/import_export_assign.rs b/crates/swc_ecma_transforms_typescript/src/import_export_assign.rs index 25f8b58c8b..5e9a3acc20 100644 --- a/crates/swc_ecma_transforms_typescript/src/import_export_assign.rs +++ b/crates/swc_ecma_transforms_typescript/src/import_export_assign.rs @@ -56,7 +56,7 @@ impl VisitMut for ImportExportAssign { .into()], src: Box::new(quote_str!("module")), type_only: false, - asserts: None, + with: None, }) .into(), ); @@ -160,7 +160,7 @@ impl VisitMut for ImportExportAssign { .into()], src: None, type_only: false, - asserts: None, + with: None, } .into(), )) diff --git a/crates/swc_ecma_transforms_typescript/src/strip.rs b/crates/swc_ecma_transforms_typescript/src/strip.rs index 6c7419892f..97bbf218cc 100644 --- a/crates/swc_ecma_transforms_typescript/src/strip.rs +++ b/crates/swc_ecma_transforms_typescript/src/strip.rs @@ -2205,7 +2205,7 @@ where specifiers: vec![], src: None, type_only: false, - asserts: None, + with: None, }, ))) } diff --git a/crates/swc_ecma_transforms_typescript/tests/strip.rs b/crates/swc_ecma_transforms_typescript/tests/strip.rs index 549a7ee9a6..08326e3ff0 100644 --- a/crates/swc_ecma_transforms_typescript/tests/strip.rs +++ b/crates/swc_ecma_transforms_typescript/tests/strip.rs @@ -729,7 +729,7 @@ test!( constructor(a) { } } - (()=>{ A.b = 'foo'; })();" + A.b = 'foo';" ); test!( @@ -3146,7 +3146,20 @@ test!( decorators: true, ..Default::default() }), - |_| chain!(tr(), optional_chaining(Default::default())), + |_| { + let unresolved_mark = Mark::new(); + let top_level_mark = Mark::new(); + let config = strip::Config { + no_empty_export: true, + ..Default::default() + }; + chain!( + Optional::new(decorators(Default::default()), false,), + resolver(unresolved_mark, top_level_mark, true), + strip_with_config(config, top_level_mark), + optional_chaining(Default::default(), unresolved_mark) + ) + }, issue_1149_1, " const tmp = tt?.map((t: any) => t).join((v: any) => v); @@ -3668,9 +3681,7 @@ to!( prop = (console.log(1), 'a'); prop1 = (console.log(2), 'b'); })(); - (()=>{ - A[prop1] = 2; - })(); + A[prop1] = 2; " ); @@ -3696,9 +3707,7 @@ to!( prop = (console.log(1), 'a'); prop1 = (console.log(2), 'b'); })(); - (()=>{ - A[prop1] = 2; - })(); + A[prop1] = 2; " ); @@ -3769,7 +3778,7 @@ to!( " var _class; const A = (_class = class {}, - (()=>{ _class.a = 1; })(), + _class.a = 1, _class); " ); @@ -4144,9 +4153,9 @@ to!( var _TestClass; var _class; let TestClass = _class = someClassDecorator((_class = (_TestClass = class TestClass { - }, (()=>{ _TestClass.Something = 'hello'; })(), (()=>{ _TestClass.SomeProperties = { + }, _TestClass.Something = 'hello', _TestClass.SomeProperties = { firstProp: _TestClass.Something - };})(), _TestClass)) || _class) || _class; + }, _TestClass)) || _class) || _class; function someClassDecorator(c) { return c; } @@ -4210,7 +4219,7 @@ class Foo { const identifier = 'bar'; class Foo { } -(()=>{ Foo.identifier = 5; })(); +Foo.identifier = 5; " ); diff --git a/crates/swc_ecma_transforms_typescript/tests/strip_correctness.rs b/crates/swc_ecma_transforms_typescript/tests/strip_correctness.rs index fe22e0b1e6..21f42d1011 100644 --- a/crates/swc_ecma_transforms_typescript/tests/strip_correctness.rs +++ b/crates/swc_ecma_transforms_typescript/tests/strip_correctness.rs @@ -188,10 +188,7 @@ fn identity(entry: PathBuf) { { let mut emitter = Emitter { - cfg: swc_ecma_codegen::Config { - minify: false, - ..Default::default() - }, + cfg: swc_ecma_codegen::Config::default(), cm: cm.clone(), wr: Box::new(swc_ecma_codegen::text_writer::JsWriter::new( cm.clone(), @@ -239,7 +236,7 @@ fn identity(entry: PathBuf) { decorators: true, decorators_before_export: true, export_default_from: true, - import_assertions: true, + import_attributes: true, allow_super_outside_method: true, ..Default::default() }), diff --git a/crates/swc_ecma_usage_analyzer/src/analyzer/mod.rs b/crates/swc_ecma_usage_analyzer/src/analyzer/mod.rs index a8fa1cdbd8..c4fa2462bc 100644 --- a/crates/swc_ecma_usage_analyzer/src/analyzer/mod.rs +++ b/crates/swc_ecma_usage_analyzer/src/analyzer/mod.rs @@ -919,8 +919,8 @@ where v.add_accessed_property(prop.sym.clone()); } - if self.ctx.in_assign_lhs || self.ctx.is_delete_arg { - self.data.mark_property_mutattion(obj.to_id(), self.ctx) + if self.ctx.in_assign_lhs || self.ctx.in_update_arg || self.ctx.is_delete_arg { + self.data.mark_property_mutation(obj.to_id(), self.ctx) } }) } diff --git a/crates/swc_ecma_usage_analyzer/src/analyzer/storage.rs b/crates/swc_ecma_usage_analyzer/src/analyzer/storage.rs index c15bcfdb41..3abb2c422e 100644 --- a/crates/swc_ecma_usage_analyzer/src/analyzer/storage.rs +++ b/crates/swc_ecma_usage_analyzer/src/analyzer/storage.rs @@ -30,7 +30,7 @@ pub trait Storage: Sized + Default { fn get_initialized_cnt(&self) -> usize; fn truncate_initialized_cnt(&mut self, len: usize); - fn mark_property_mutattion(&mut self, id: Id, ctx: Ctx); + fn mark_property_mutation(&mut self, id: Id, ctx: Ctx); } pub trait ScopeDataLike: Sized + Default + Clone { @@ -65,10 +65,6 @@ pub trait VarDataLike: Sized { fn add_accessed_property(&mut self, name: JsWord); - fn mark_mutated(&mut self); - - fn mark_reassigned(&mut self); - fn mark_used_as_ref(&mut self); fn add_infects_to(&mut self, other: Access); diff --git a/crates/swc_ecma_visit/src/lib.rs b/crates/swc_ecma_visit/src/lib.rs index 346d91364e..788cd818c4 100644 --- a/crates/swc_ecma_visit/src/lib.rs +++ b/crates/swc_ecma_visit/src/lib.rs @@ -1095,20 +1095,20 @@ define!({ pub specifiers: Vec, pub src: Box, pub type_only: bool, - pub asserts: Option>, + pub with: Option>, } pub struct ExportAll { pub span: Span, pub src: Box, pub type_only: bool, - pub asserts: Option>, + pub with: Option>, } pub struct NamedExport { pub span: Span, pub specifiers: Vec, pub src: Option>, pub type_only: bool, - pub asserts: Option>, + pub with: Option>, } pub struct ExportDefaultDecl { pub span: Span, diff --git a/crates/swc_estree_ast/src/module.rs b/crates/swc_estree_ast/src/module.rs index 70fd9ba1ff..54e75758e7 100644 --- a/crates/swc_estree_ast/src/module.rs +++ b/crates/swc_estree_ast/src/module.rs @@ -143,7 +143,7 @@ pub struct ExportAllDeclaration { pub base: BaseNode, pub source: StringLiteral, #[serde(default)] - pub assertions: Option>, + pub with: Option>, #[serde(default)] pub export_kind: Option, } @@ -192,7 +192,7 @@ pub struct ExportNamedDeclaration { #[serde(default)] pub source: Option, #[serde(default)] - pub assertions: Option>, + pub with: Option>, #[serde(default)] pub export_kind: Option, } @@ -270,7 +270,7 @@ pub struct ImportDeclaration { pub specifiers: Vec, pub source: StringLiteral, #[serde(default)] - pub assertions: Option>, + pub with: Option>, #[serde(default)] pub import_kind: Option, } diff --git a/crates/swc_estree_compat/benches/babelify.rs b/crates/swc_estree_compat/benches/babelify.rs index 7b0b94f68c..5076f7615e 100644 --- a/crates/swc_estree_compat/benches/babelify.rs +++ b/crates/swc_estree_compat/benches/babelify.rs @@ -55,7 +55,7 @@ fn babelify_only(b: &mut Bencher) { module .fold_with(&mut resolver(unresolved_mark, top_level_mark, true)) .fold_with(&mut typescript::strip(top_level_mark)) - .fold_with(&mut es2020(Default::default())) + .fold_with(&mut es2020(Default::default(), unresolved_mark)) }); b.iter(|| { diff --git a/crates/swc_estree_compat/src/babelify/module_decl.rs b/crates/swc_estree_compat/src/babelify/module_decl.rs index 4d49dc8c37..91fea257b3 100644 --- a/crates/swc_estree_compat/src/babelify/module_decl.rs +++ b/crates/swc_estree_compat/src/babelify/module_decl.rs @@ -90,13 +90,13 @@ impl Babelify for ExportDecl { declaration: Some(Box::alloc().init(self.decl.babelify(ctx))), specifiers: Default::default(), source: Default::default(), - assertions: Default::default(), + with: Default::default(), export_kind: Default::default(), } } } -fn convert_import_asserts( +fn convert_import_attrs( asserts: Option>, ctx: &Context, ) -> Option> { @@ -161,7 +161,7 @@ impl Babelify for ImportDecl { base: ctx.base(self.span), specifiers: self.specifiers.babelify(ctx), source: self.src.babelify(ctx), - assertions: convert_import_asserts(self.asserts, ctx), + with: convert_import_attrs(self.with, ctx), import_kind: if self.type_only { Some(ImportKind::Type) } else { @@ -178,7 +178,7 @@ impl Babelify for ExportAll { ExportAllDeclaration { base: ctx.base(self.span), source: self.src.babelify(ctx), - assertions: convert_import_asserts(self.asserts, ctx), + with: convert_import_attrs(self.with, ctx), export_kind: if self.type_only { Some(ExportKind::Type) } else { @@ -197,7 +197,7 @@ impl Babelify for NamedExport { declaration: Default::default(), specifiers: self.specifiers.babelify(ctx), source: self.src.map(|s| s.babelify(ctx)), - assertions: convert_import_asserts(self.asserts, ctx), + with: convert_import_attrs(self.with, ctx), export_kind: if self.type_only { Some(ExportKind::Type) } else { diff --git a/crates/swc_estree_compat/src/swcify/stmt.rs b/crates/swc_estree_compat/src/swcify/stmt.rs index 80bae36d8e..4b806296ec 100644 --- a/crates/swc_estree_compat/src/swcify/stmt.rs +++ b/crates/swc_estree_compat/src/swcify/stmt.rs @@ -436,8 +436,8 @@ impl Swcify for ExportAllDeclaration { span: ctx.span(&self.base), src: self.source.swcify(ctx).into(), type_only: self.export_kind == Some(ExportKind::Type), - asserts: self - .assertions + with: self + .with .swcify(ctx) .map(|props| { props @@ -528,8 +528,8 @@ impl Swcify for ExportNamedDeclaration { specifiers: self.specifiers.swcify(ctx), src: self.source.swcify(ctx).map(Box::new), type_only: false, - asserts: self - .assertions + with: self + .with .swcify(ctx) .map(|props| { props @@ -625,8 +625,8 @@ impl Swcify for ImportDeclaration { specifiers: self.specifiers.swcify(ctx), src: self.source.swcify(ctx).into(), type_only: false, - asserts: self - .assertions + with: self + .with .swcify(ctx) .map(|props| { props @@ -779,7 +779,7 @@ impl Swcify for DeclareExportAllDeclaration { span: ctx.span(&self.base), src: self.source.swcify(ctx).into(), type_only: self.export_kind == Some(ExportKind::Type), - asserts: Default::default(), + with: Default::default(), } } } diff --git a/crates/swc_html_minifier/src/lib.rs b/crates/swc_html_minifier/src/lib.rs index bca0f876bf..4a83a7d9aa 100644 --- a/crates/swc_html_minifier/src/lib.rs +++ b/crates/swc_html_minifier/src/lib.rs @@ -2114,12 +2114,7 @@ impl Minifier<'_> { )) as Box; let mut emitter = swc_ecma_codegen::Emitter { - cfg: swc_ecma_codegen::Config { - target, - minify: false, - ascii_only: false, - omit_last_semi: false, - }, + cfg: swc_ecma_codegen::Config::default().with_target(target), cm, comments: Some(&comments), wr, diff --git a/crates/swc_html_parser/src/lexer/mod.rs b/crates/swc_html_parser/src/lexer/mod.rs index eb03d36877..f63a2eec62 100644 --- a/crates/swc_html_parser/src/lexer/mod.rs +++ b/crates/swc_html_parser/src/lexer/mod.rs @@ -152,7 +152,10 @@ where // A leading Byte Order Mark (BOM) causes the character encoding argument to be // ignored and will itself be skipped. if lexer.input.is_at_start() && lexer.input.cur() == Some('\u{feff}') { - lexer.input.bump(); + unsafe { + // Safety: We know that the current character is '\u{feff}'. + lexer.input.bump(); + } } lexer @@ -240,13 +243,19 @@ where self.cur_pos = self.input.cur_pos(); if self.cur.is_some() { - self.input.bump(); + unsafe { + // Safety: self.cur is Some() + self.input.bump(); + } } } #[inline(always)] fn reconsume(&mut self) { - self.input.reset_to(self.cur_pos); + unsafe { + // Safety: self.cur_pos is valid position because we got it from self.input + self.input.reset_to(self.cur_pos); + } } #[inline(always)] @@ -401,7 +410,10 @@ where sub_buf.push(c); if self.input.cur() == Some('\n') { - self.input.bump(); + unsafe { + // Safety: cur() is Some('\n') + self.input.bump(); + } sub_buf.push('\n'); } @@ -466,7 +478,10 @@ where sub_buf.push(c); if self.input.cur() == Some('\n') { - self.input.bump(); + unsafe { + // Safety: cur() is Some('\n') + self.input.bump(); + } sub_buf.push('\n'); } @@ -497,7 +512,10 @@ where sub_buf.push(c); if self.input.cur() == Some('\n') { - self.input.bump(); + unsafe { + // Safety: cur() is Some('\n') + self.input.bump(); + } sub_buf.push('\n'); } @@ -795,7 +813,10 @@ where sub_buf.push('\r'); if self.input.cur() == Some('\n') { - self.input.bump(); + unsafe { + // Safety: cur() is Some('\n') + self.input.bump(); + } sub_buf.push('\n'); } @@ -826,7 +847,10 @@ where sub_buf.push(c); if self.input.cur() == Some('\n') { - self.input.bump(); + unsafe { + // Safety: cur() is Some('\n') + self.input.bump(); + } sub_buf.push('\n'); } @@ -955,7 +979,10 @@ where sub_buf.push(c); if self.input.cur() == Some('\n') { - self.input.bump(); + unsafe { + // Safety: cur() is Some('\n') + self.input.bump(); + } sub_buf.push('\n'); } @@ -1022,8 +1049,10 @@ where buf.push(c); if self.input.cur() == Some('\n') { - self.input.bump(); - + unsafe { + // Safety: cur() is Some('\n') + self.input.bump(); + } buf.push('\n'); } @@ -2861,7 +2890,10 @@ where lexer.state = State::BogusComment; lexer.cur_pos = cur_pos; // We don't validate input here because we reset position - lexer.input.reset_to(cur_pos); + unsafe { + // Safety: We reset position to the previous one + lexer.input.reset_to(cur_pos); + } }; // If the next few characters are: @@ -3536,7 +3568,11 @@ where _ => { buf.clear(); self.cur_pos = cur_pos; - self.input.reset_to(cur_pos); + unsafe { + // Safety: We got cur_pos from self.input.cur_pos() above, so + // it's a valid position. + self.input.reset_to(cur_pos); + } self.emit_error( ErrorKind::InvalidCharacterSequenceAfterDoctypeName, ); @@ -4393,10 +4429,16 @@ where if entity.is_some() { self.cur_pos = entity_cur_pos.unwrap(); - self.input.reset_to(entity_cur_pos.unwrap()); + unsafe { + // Safety: We got entity_cur_pos from the input, so it's valid + self.input.reset_to(entity_cur_pos.unwrap()); + } } else { self.cur_pos = initial_cur_pos; - self.input.reset_to(initial_cur_pos); + unsafe { + // Safety: We got initial_cur_pos from the input, so it's valid + self.input.reset_to(initial_cur_pos); + } } let is_last_semicolon = self.temporary_buffer.ends_with(';'); @@ -4814,7 +4856,10 @@ where #[inline(always)] fn skip_whitespaces(&mut self, c: char) { if c == '\r' && self.input.cur() == Some('\n') { - self.input.bump(); + unsafe { + // Safety: cur() is Some + self.input.bump(); + } } } } diff --git a/crates/swc_node_bundler/tests/fixture.rs b/crates/swc_node_bundler/tests/fixture.rs index 9ecac943b3..237f37a622 100644 --- a/crates/swc_node_bundler/tests/fixture.rs +++ b/crates/swc_node_bundler/tests/fixture.rs @@ -91,15 +91,13 @@ fn pass(input_dir: PathBuf) { None, None, false, - EsVersion::Es2020, SourceMapsConfig::Bool(false), &Default::default(), None, - false, Some(&comments), false, - false, Default::default(), + swc_ecma_codegen::Config::default().with_target(EsVersion::Es2020), ) .expect("failed to print?") .code; diff --git a/crates/swc_plugin_runner/src/wasix_runtime.rs b/crates/swc_plugin_runner/src/wasix_runtime.rs index 09e8d0b54e..1fa2fb936a 100644 --- a/crates/swc_plugin_runner/src/wasix_runtime.rs +++ b/crates/swc_plugin_runner/src/wasix_runtime.rs @@ -1,3 +1,5 @@ +#![allow(unused)] + use std::{path::PathBuf, sync::Arc}; use parking_lot::Mutex; diff --git a/crates/swc_xml_parser/src/lexer/mod.rs b/crates/swc_xml_parser/src/lexer/mod.rs index 1b259f65ad..85eb3e0659 100644 --- a/crates/swc_xml_parser/src/lexer/mod.rs +++ b/crates/swc_xml_parser/src/lexer/mod.rs @@ -171,7 +171,10 @@ where // A leading Byte Order Mark (BOM) causes the character encoding argument to be // ignored and will itself be skipped. if lexer.input.is_at_start() && lexer.input.cur() == Some('\u{feff}') { - lexer.input.bump(); + unsafe { + // Safety: cur() is Some('\u{feff}') + lexer.input.bump(); + } } lexer @@ -247,13 +250,19 @@ where self.cur_pos = self.input.cur_pos(); if self.cur.is_some() { - self.input.bump(); + unsafe { + // Safety: cur() is Some(c) + self.input.bump(); + } } } #[inline(always)] fn reconsume(&mut self) { - self.input.reset_to(self.cur_pos); + unsafe { + // Safety: We got cur_pos from self.input + self.input.reset_to(self.cur_pos); + } } #[inline(always)] @@ -299,7 +308,10 @@ where let anything_else = |lexer: &mut Lexer| { lexer.emit_error(ErrorKind::InvalidEntityCharacter); lexer.cur_pos = cur_pos; - lexer.input.reset_to(cur_pos); + unsafe { + // Safety: We got cur_post from self.input + lexer.input.reset_to(cur_pos); + } }; // This section defines how to consume a character reference, optionally with an @@ -318,7 +330,10 @@ where Some(c) if self.additional_allowed_character == Some(c) => { self.emit_error(ErrorKind::InvalidEntityCharacter); self.cur_pos = cur_pos; - self.input.reset_to(cur_pos); + unsafe { + // Safety: We got cur_post from self.input + self.input.reset_to(cur_pos); + } } Some('l') => match self.consume_next_char() { Some('t') => { @@ -467,7 +482,10 @@ where if characters.is_empty() { // TODO self.cur_pos = cur_pos; - self.input.reset_to(cur_pos); + unsafe { + // Safety: We got cur_post from self.input + self.input.reset_to(cur_pos); + } return None; } @@ -553,7 +571,10 @@ where raw.push(c); if self.input.cur() == Some('\n') { - self.input.bump(); + unsafe { + // Safety: cur() is Some('\n') + self.input.bump(); + } raw.push('\n'); } @@ -873,7 +894,10 @@ where raw_c.push(c); if self.input.cur() == Some('\n') { - self.input.bump(); + unsafe { + // Safety: cur() is Some('\n') + self.input.bump(); + } raw_c.push('\n'); } @@ -937,7 +961,10 @@ where raw.push(c); if self.input.cur() == Some('\n') { - self.input.bump(); + unsafe { + // Safety: cur() is Some('\n') + self.input.bump(); + } raw.push('\n'); } @@ -1236,7 +1263,10 @@ where lexer.state = State::BogusComment; lexer.cur_pos = cur_pos; // We don't validate input here because we reset position - lexer.input.reset_to(cur_pos); + unsafe { + // Safety: cur_pos is in the range of input + lexer.input.reset_to(cur_pos); + } }; // If the next few characters are: @@ -2464,7 +2494,10 @@ where } _ => { self.cur_pos = cur_pos; - self.input.reset_to(cur_pos); + unsafe { + // Safety: We got cur_pos from self.input.cur_pos() + self.input.reset_to(cur_pos); + } self.emit_error( ErrorKind::InvalidCharacterSequenceAfterDoctypeName, ); @@ -3074,7 +3107,10 @@ where #[inline(always)] fn skip_next_lf(&mut self, c: char) { if c == '\r' && self.input.cur() == Some('\n') { - self.input.bump(); + unsafe { + // Safety: cur() is Some('\n') + self.input.bump(); + } } } }