1#![deny(unused)]
108#![allow(clippy::too_many_arguments)]
109#![allow(clippy::mutable_key_type)]
110#![cfg_attr(docsrs, feature(doc_cfg))]
111
112pub extern crate swc_atoms as atoms;
113extern crate swc_common as common;
114
115use std::{
116 fs::{read_to_string, File},
117 io::ErrorKind,
118 path::{Path, PathBuf},
119 sync::Arc,
120};
121
122use anyhow::{bail, Context, Error};
123use base64::prelude::{Engine, BASE64_STANDARD};
124use common::{
125 comments::{Comment, SingleThreadedComments},
126 errors::HANDLER,
127};
128use jsonc_parser::{parse_to_serde_value, ParseOptions};
129use once_cell::sync::Lazy;
130use serde_json::error::Category;
131use swc_common::{
132 comments::Comments, errors::Handler, sync::Lrc, FileName, Mark, SourceFile, SourceMap, Spanned,
133 GLOBALS,
134};
135pub use swc_compiler_base::{PrintArgs, TransformOutput};
136pub use swc_config::types::{BoolConfig, BoolOr, BoolOrDataConfig};
137use swc_ecma_ast::{noop_pass, EsVersion, Pass, Program};
138use swc_ecma_codegen::Node;
139#[cfg(feature = "module")]
140use swc_ecma_loader::resolvers::{
141 lru::CachingResolver, node::NodeModulesResolver, tsc::TsConfigResolver,
142};
143use swc_ecma_minifier::option::{MangleCache, MinifyOptions, TopLevelOptions};
144use swc_ecma_parser::{EsSyntax, Syntax};
145use swc_ecma_transforms::{
146 fixer,
147 helpers::{self, Helpers},
148 hygiene, resolver,
149};
150use swc_ecma_transforms_base::fixer::paren_remover;
151#[cfg(feature = "module")]
152use swc_ecma_transforms_module::path::NodeImportResolver;
153use swc_ecma_visit::{FoldWith, VisitMutWith, VisitWith};
154pub use swc_error_reporters::handler::{try_with_handler, HandlerOpts};
155pub use swc_node_comments::SwcComments;
156pub use swc_sourcemap as sourcemap;
157use swc_timer::timer;
158#[cfg(feature = "isolated-dts")]
159use swc_typescript::fast_dts::FastDts;
160use tracing::warn;
161use url::Url;
162
163use crate::config::{
164 BuiltInput, Config, ConfigFile, InputSourceMap, IsModule, JsMinifyCommentOption,
165 JsMinifyOptions, Options, OutputCharset, Rc, RootMode, SourceMapsConfig,
166};
167
168mod builder;
169pub mod config;
170mod dropped_comments_preserver;
171mod plugin;
172pub mod wasm_analysis;
173pub mod resolver {
174 use std::path::PathBuf;
175
176 use rustc_hash::FxHashMap;
177 use swc_ecma_loader::{
178 resolvers::{lru::CachingResolver, node::NodeModulesResolver, tsc::TsConfigResolver},
179 TargetEnv,
180 };
181
182 use crate::config::CompiledPaths;
183
184 pub type NodeResolver = CachingResolver<NodeModulesResolver>;
185
186 pub fn paths_resolver(
187 target_env: TargetEnv,
188 alias: FxHashMap<String, String>,
189 base_url: PathBuf,
190 paths: CompiledPaths,
191 preserve_symlinks: bool,
192 ) -> CachingResolver<TsConfigResolver<NodeModulesResolver>> {
193 let r = TsConfigResolver::new(
194 NodeModulesResolver::without_node_modules(target_env, alias, preserve_symlinks),
195 base_url,
196 paths,
197 );
198 CachingResolver::new(40, r)
199 }
200
201 pub fn environment_resolver(
202 target_env: TargetEnv,
203 alias: FxHashMap<String, String>,
204 preserve_symlinks: bool,
205 ) -> NodeResolver {
206 CachingResolver::new(
207 40,
208 NodeModulesResolver::new(target_env, alias, preserve_symlinks),
209 )
210 }
211}
212
213#[cfg(feature = "module")]
214type SwcImportResolver = Arc<
215 NodeImportResolver<CachingResolver<TsConfigResolver<CachingResolver<NodeModulesResolver>>>>,
216>;
217
218pub struct Compiler {
223 pub cm: Arc<SourceMap>,
225 comments: SwcComments,
226}
227
228impl Compiler {
230 pub fn comments(&self) -> &SwcComments {
231 &self.comments
232 }
233
234 pub fn run<R, F>(&self, op: F) -> R
238 where
239 F: FnOnce() -> R,
240 {
241 debug_assert!(
242 GLOBALS.is_set(),
243 "`swc_common::GLOBALS` is required for this operation"
244 );
245
246 op()
247 }
248
249 fn get_orig_src_map(
250 &self,
251 fm: &SourceFile,
252 input_src_map: &InputSourceMap,
253 comments: &[Comment],
254 is_default: bool,
255 ) -> Result<Option<sourcemap::SourceMap>, Error> {
256 self.run(|| -> Result<_, Error> {
257 let name = &fm.name;
258
259 let read_inline_sourcemap =
260 |data_url: &str| -> Result<Option<sourcemap::SourceMap>, Error> {
261 let url = Url::parse(data_url).with_context(|| {
262 format!("failed to parse inline source map url\n{data_url}")
263 })?;
264
265 let idx = match url.path().find("base64,") {
266 Some(v) => v,
267 None => {
268 bail!("failed to parse inline source map: not base64: {url:?}")
269 }
270 };
271
272 let content = url.path()[idx + "base64,".len()..].trim();
273
274 let res = BASE64_STANDARD
275 .decode(content.as_bytes())
276 .context("failed to decode base64-encoded source map")?;
277
278 Ok(Some(sourcemap::SourceMap::from_slice(&res).context(
279 "failed to read input source map from inlined base64 encoded string",
280 )?))
281 };
282
283 let read_file_sourcemap =
284 |data_url: Option<&str>| -> Result<Option<sourcemap::SourceMap>, Error> {
285 match &**name {
286 FileName::Real(filename) => {
287 let dir = match filename.parent() {
288 Some(v) => v,
289 None => {
290 bail!("unexpected: root directory is given as a input file")
291 }
292 };
293
294 let map_path = match data_url {
295 Some(data_url) => {
296 let mut map_path = dir.join(data_url);
297 if !map_path.exists() {
298 let fallback_map_path =
306 PathBuf::from(format!("{}.map", filename.display()));
307 if fallback_map_path.exists() {
308 map_path = fallback_map_path;
309 } else {
310 bail!(
311 "failed to find input source map file {:?} in \
312 {:?} file as either {:?} or with appended .map",
313 data_url,
314 filename.display(),
315 map_path.display(),
316 )
317 }
318 }
319
320 Some(map_path)
321 }
322 None => {
323 let map_path =
325 PathBuf::from(format!("{}.map", filename.display()));
326 if map_path.exists() {
327 Some(map_path)
328 } else {
329 None
330 }
331 }
332 };
333
334 match map_path {
335 Some(map_path) => {
336 let path = map_path.display().to_string();
337 let file = File::open(&path);
338
339 if file
345 .as_ref()
346 .is_err_and(|err| err.kind() == ErrorKind::NotFound)
347 {
348 warn!(
349 "source map is specified by sourceMappingURL but \
350 there's no source map at `{}`",
351 path
352 );
353 return Ok(None);
354 }
355
356 let file = if !is_default {
358 file?
359 } else {
360 match file {
361 Ok(v) => v,
362 Err(_) => return Ok(None),
363 }
364 };
365
366 Ok(Some(sourcemap::SourceMap::from_reader(file).with_context(
367 || {
368 format!(
369 "failed to read input source map
370 from file at {path}"
371 )
372 },
373 )?))
374 }
375 None => Ok(None),
376 }
377 }
378 _ => Ok(None),
379 }
380 };
381
382 let read_sourcemap = || -> Option<sourcemap::SourceMap> {
383 let s = "sourceMappingURL=";
384
385 let text = comments.iter().rev().find_map(|c| {
386 let idx = c.text.rfind(s)?;
387 let (_, url) = c.text.split_at(idx + s.len());
388
389 Some(url.trim())
390 });
391
392 let result = match text {
394 Some(text) if text.starts_with("data:") => read_inline_sourcemap(text),
395 _ => read_file_sourcemap(text),
396 };
397 match result {
398 Ok(r) => r,
399 Err(err) => {
400 tracing::error!("failed to read input source map: {:?}", err);
401 None
402 }
403 }
404 };
405
406 match input_src_map {
408 InputSourceMap::Bool(false) => Ok(None),
409 InputSourceMap::Bool(true) => Ok(read_sourcemap()),
410 InputSourceMap::Str(ref s) => {
411 if s == "inline" {
412 Ok(read_sourcemap())
413 } else {
414 Ok(Some(
416 swc_sourcemap::SourceMap::from_slice(s.as_bytes()).context(
417 "failed to read input source map from user-provided sourcemap",
418 )?,
419 ))
420 }
421 }
422 }
423 })
424 }
425
426 pub fn parse_js(
428 &self,
429 fm: Arc<SourceFile>,
430 handler: &Handler,
431 target: EsVersion,
432 syntax: Syntax,
433 is_module: IsModule,
434 comments: Option<&dyn Comments>,
435 ) -> Result<Program, Error> {
436 swc_compiler_base::parse_js(
437 self.cm.clone(),
438 fm,
439 handler,
440 target,
441 syntax,
442 is_module,
443 comments,
444 )
445 }
446
447 #[allow(clippy::too_many_arguments)]
453 pub fn print<T>(&self, node: &T, args: PrintArgs) -> Result<TransformOutput, Error>
454 where
455 T: Node + VisitWith<swc_compiler_base::IdentCollector>,
456 {
457 swc_compiler_base::print(self.cm.clone(), node, args)
458 }
459}
460
461impl Compiler {
463 pub fn new(cm: Arc<SourceMap>) -> Self {
464 Compiler {
465 cm,
466 comments: Default::default(),
467 }
468 }
469
470 #[tracing::instrument(skip_all)]
471 pub fn read_config(&self, opts: &Options, name: &FileName) -> Result<Option<Config>, Error> {
472 static CUR_DIR: Lazy<PathBuf> = Lazy::new(|| {
473 if cfg!(target_arch = "wasm32") {
474 PathBuf::new()
475 } else {
476 ::std::env::current_dir().unwrap()
477 }
478 });
479
480 self.run(|| -> Result<_, Error> {
481 let Options {
482 ref root,
483 root_mode,
484 swcrc,
485 config_file,
486 ..
487 } = opts;
488
489 let root = root.as_ref().unwrap_or(&CUR_DIR);
490
491 let swcrc_path = match config_file {
492 Some(ConfigFile::Str(s)) => Some(PathBuf::from(s.clone())),
493 _ => {
494 if *swcrc {
495 if let FileName::Real(ref path) = name {
496 let abs_path = if path.is_relative() {
498 root.join(path).canonicalize().ok()
499 } else {
500 path.canonicalize().ok()
501 };
502 let found = abs_path.and_then(|p| find_swcrc(&p, root, *root_mode));
503
504 if found.is_none() && *root_mode == RootMode::Upward {
506 bail!(
507 "Could not find .swcrc file while using rootMode \
508 \"upward\".\nSearched from: {}",
509 path.display()
510 );
511 }
512
513 found
514 } else {
515 None
516 }
517 } else {
518 None
519 }
520 }
521 };
522
523 let config_file = match swcrc_path.as_deref() {
524 Some(s) => Some(load_swcrc(s)?),
525 _ => None,
526 };
527 let filename_path = match name {
528 FileName::Real(p) => Some(&**p),
529 _ => None,
530 };
531
532 if let Some(filename_path) = filename_path {
533 if let Some(config) = config_file {
534 let dir = swcrc_path
535 .as_deref()
536 .and_then(|p| p.parent())
537 .expect(".swcrc path should have parent dir");
538
539 let mut config = config
540 .into_config(Some(filename_path))
541 .context("failed to process config file")?;
542
543 if let Some(c) = &mut config {
544 if c.jsc.base_url != PathBuf::new() {
545 let joined = dir.join(&c.jsc.base_url);
546 c.jsc.base_url = if cfg!(target_os = "windows")
547 && c.jsc.base_url.as_os_str() == "."
548 {
549 dir.canonicalize().with_context(|| {
550 format!(
551 "failed to canonicalize base url using the path of \
552 .swcrc\nDir: {}\n(Used logic for windows)",
553 dir.display(),
554 )
555 })?
556 } else {
557 joined.canonicalize().with_context(|| {
558 format!(
559 "failed to canonicalize base url using the path of \
560 .swcrc\nPath: {}\nDir: {}\nbaseUrl: {}",
561 joined.display(),
562 dir.display(),
563 c.jsc.base_url.display()
564 )
565 })?
566 };
567 }
568 }
569
570 return Ok(config);
571 }
572
573 let config_file = config_file.unwrap_or_default();
574 let config = config_file.into_config(Some(filename_path))?;
575
576 return Ok(config);
577 }
578
579 let config = match config_file {
580 Some(config_file) => config_file.into_config(None)?,
581 None => Rc::default().into_config(None)?,
582 };
583
584 match config {
585 Some(config) => Ok(Some(config)),
586 None => {
587 bail!("no config matched for file ({name})")
588 }
589 }
590 })
591 .with_context(|| format!("failed to read .swcrc file for input file at `{name}`"))
592 }
593
594 #[tracing::instrument(skip_all)]
600 pub fn parse_js_as_input<'a, P>(
601 &'a self,
602 fm: Lrc<SourceFile>,
603 program: Option<Program>,
604 handler: &'a Handler,
605 opts: &Options,
606 name: &FileName,
607 comments: Option<&'a SingleThreadedComments>,
608 before_pass: impl 'a + FnOnce(&Program) -> P,
609 ) -> Result<Option<BuiltInput<impl 'a + Pass>>, Error>
610 where
611 P: 'a + Pass,
612 {
613 self.run(move || {
614 let _timer = timer!("Compiler.parse");
615
616 if let FileName::Real(ref path) = name {
617 if !opts.config.matches(path)? {
618 return Ok(None);
619 }
620 }
621
622 let config = self.read_config(opts, name)?;
623 let config = match config {
624 Some(v) => v,
625 None => return Ok(None),
626 };
627
628 let built = opts.build_as_input(
629 &self.cm,
630 name,
631 move |syntax, target, is_module| match program {
632 Some(v) => Ok(v),
633 _ => self.parse_js(
634 fm.clone(),
635 handler,
636 target,
637 syntax,
638 is_module,
639 comments.as_ref().map(|v| v as _),
640 ),
641 },
642 opts.output_path.as_deref(),
643 opts.source_root.clone(),
644 opts.source_file_name.clone(),
645 config.source_map_ignore_list.clone(),
646 handler,
647 Some(config),
648 comments,
649 before_pass,
650 )?;
651 Ok(Some(built))
652 })
653 }
654
655 pub fn run_transform<F, Ret>(&self, handler: &Handler, external_helpers: bool, op: F) -> Ret
656 where
657 F: FnOnce() -> Ret,
658 {
659 self.run(|| {
660 helpers::HELPERS.set(&Helpers::new(external_helpers), || HANDLER.set(handler, op))
661 })
662 }
663
664 #[tracing::instrument(skip_all)]
665 pub fn transform(
666 &self,
667 handler: &Handler,
668 program: Program,
669 external_helpers: bool,
670 mut pass: impl swc_ecma_visit::Fold,
671 ) -> Program {
672 self.run_transform(handler, external_helpers, || {
673 program.fold_with(&mut pass)
675 })
676 }
677
678 #[tracing::instrument(skip_all)]
693 pub fn process_js_with_custom_pass<P1, P2>(
694 &self,
695 fm: Arc<SourceFile>,
696 program: Option<Program>,
697 handler: &Handler,
698 opts: &Options,
699 comments: SingleThreadedComments,
700 custom_before_pass: impl FnOnce(&Program) -> P1,
701 custom_after_pass: impl FnOnce(&Program) -> P2,
702 ) -> Result<TransformOutput, Error>
703 where
704 P1: Pass,
705 P2: Pass,
706 {
707 self.run(|| -> Result<_, Error> {
708 let config = self.run(|| {
709 self.parse_js_as_input(
710 fm.clone(),
711 program,
712 handler,
713 opts,
714 &fm.name,
715 Some(&comments),
716 |program| custom_before_pass(program),
717 )
718 })?;
719 let config = match config {
720 Some(v) => v,
721 None => {
722 bail!("cannot process file because it's ignored by .swcrc")
723 }
724 };
725
726 let after_pass = custom_after_pass(&config.program);
727
728 let config = config.with_pass(|pass| (pass, after_pass));
729
730 let orig = if config.source_maps.enabled() {
731 self.get_orig_src_map(
732 &fm,
733 &config.input_source_map,
734 config
735 .comments
736 .get_trailing(config.program.span_hi())
737 .as_deref()
738 .unwrap_or_default(),
739 false,
740 )?
741 } else {
742 None
743 };
744
745 self.apply_transforms(handler, comments.clone(), fm.clone(), orig, config)
746 })
747 }
748
749 #[tracing::instrument(skip(self, handler, opts))]
750 pub fn process_js_file(
751 &self,
752 fm: Arc<SourceFile>,
753 handler: &Handler,
754 opts: &Options,
755 ) -> Result<TransformOutput, Error> {
756 self.process_js_with_custom_pass(
757 fm,
758 None,
759 handler,
760 opts,
761 SingleThreadedComments::default(),
762 |_| noop_pass(),
763 |_| noop_pass(),
764 )
765 }
766
767 #[tracing::instrument(skip_all)]
768 pub fn minify(
769 &self,
770 fm: Arc<SourceFile>,
771 handler: &Handler,
772 opts: &JsMinifyOptions,
773 extras: JsMinifyExtras,
774 ) -> Result<TransformOutput, Error> {
775 self.run(|| {
776 let _timer = timer!("Compiler::minify");
777
778 let target = opts.ecma.clone().into();
779
780 let (source_map, orig, source_map_url) = opts
781 .source_map
782 .as_ref()
783 .map(|obj| -> Result<_, Error> {
784 let orig = obj.content.as_ref().map(|s| s.to_sourcemap()).transpose()?;
785
786 Ok((SourceMapsConfig::Bool(true), orig, obj.url.as_deref()))
787 })
788 .unwrap_as_option(|v| {
789 Some(Ok(match v {
790 Some(true) => (SourceMapsConfig::Bool(true), None, None),
791 _ => (SourceMapsConfig::Bool(false), None, None),
792 }))
793 })
794 .unwrap()?;
795
796 let mut min_opts = MinifyOptions {
797 compress: opts
798 .compress
799 .clone()
800 .unwrap_as_option(|default| match default {
801 Some(true) | None => Some(Default::default()),
802 _ => None,
803 })
804 .map(|v| v.into_config(self.cm.clone())),
805 mangle: opts
806 .mangle
807 .clone()
808 .unwrap_as_option(|default| match default {
809 Some(true) | None => Some(Default::default()),
810 _ => None,
811 }),
812 ..Default::default()
813 };
814
815 if opts.keep_fnames {
820 if let Some(opts) = &mut min_opts.compress {
821 opts.keep_fnames = true;
822 }
823 if let Some(opts) = &mut min_opts.mangle {
824 opts.keep_fn_names = true;
825 }
826 }
827
828 let comments = SingleThreadedComments::default();
829
830 let mut program = self
831 .parse_js(
832 fm.clone(),
833 handler,
834 target,
835 Syntax::Es(EsSyntax {
836 jsx: true,
837 decorators: true,
838 decorators_before_export: true,
839 import_attributes: true,
840 ..Default::default()
841 }),
842 opts.module,
843 Some(&comments),
844 )
845 .context("failed to parse input file")?;
846
847 if opts.toplevel == Some(true) || program.is_module() {
848 if let Some(opts) = &mut min_opts.compress {
849 if opts.top_level.is_none() {
850 opts.top_level = Some(TopLevelOptions { functions: true });
851 }
852 }
853
854 if let Some(opts) = &mut min_opts.mangle {
855 if opts.top_level.is_none() {
856 opts.top_level = Some(true);
857 }
858 }
859 }
860
861 let source_map_names = if source_map.enabled() {
862 let mut v = swc_compiler_base::IdentCollector {
863 names: Default::default(),
864 };
865
866 program.visit_with(&mut v);
867
868 v.names
869 } else {
870 Default::default()
871 };
872
873 let unresolved_mark = Mark::new();
874 let top_level_mark = Mark::new();
875
876 let is_mangler_enabled = min_opts.mangle.is_some();
877
878 program = self.run_transform(handler, false, || {
879 program.mutate(&mut paren_remover(Some(&comments)));
880
881 program.mutate(&mut resolver(unresolved_mark, top_level_mark, false));
882
883 let mut program = swc_ecma_minifier::optimize(
884 program,
885 self.cm.clone(),
886 Some(&comments),
887 None,
888 &min_opts,
889 &swc_ecma_minifier::option::ExtraOptions {
890 unresolved_mark,
891 top_level_mark,
892 mangle_name_cache: extras.mangle_name_cache,
893 },
894 );
895
896 if !is_mangler_enabled {
897 program.visit_mut_with(&mut hygiene())
898 }
899 program.mutate(&mut fixer(Some(&comments as &dyn Comments)));
900 program
901 });
902
903 let preserve_comments = opts
904 .format
905 .comments
906 .clone()
907 .into_inner()
908 .unwrap_or(BoolOr::Data(JsMinifyCommentOption::PreserveSomeComments));
909 swc_compiler_base::minify_file_comments(
910 &comments,
911 preserve_comments,
912 opts.format.preserve_annotations,
913 );
914
915 let ret = self.print(
916 &program,
917 PrintArgs {
918 source_root: None,
919 source_file_name: Some(&fm.name.to_string()),
920 output_path: opts.output_path.clone().map(From::from),
921 inline_sources_content: opts.inline_sources_content,
922 source_map,
923 source_map_ignore_list: opts.source_map_ignore_list.clone(),
924 source_map_names: &source_map_names,
925 orig,
926 comments: Some(&comments),
927 emit_source_map_columns: opts.emit_source_map_columns,
928 emit_source_map_scopes: false,
929 preamble: &opts.format.preamble,
930 codegen_config: swc_ecma_codegen::Config::default()
931 .with_target(target)
932 .with_minify(true)
933 .with_ascii_only(opts.format.ascii_only)
934 .with_emit_assert_for_import_attributes(
935 opts.format.emit_assert_for_import_attributes,
936 )
937 .with_inline_script(opts.format.inline_script)
938 .with_reduce_escaped_newline(
939 min_opts
940 .compress
941 .unwrap_or_default()
942 .experimental
943 .reduce_escaped_newline,
944 ),
945 output: None,
946 source_map_url,
947 },
948 );
949
950 ret.map(|mut output| {
951 output.diagnostics = handler.take_diagnostics();
952
953 output
954 })
955 })
956 }
957
958 #[tracing::instrument(skip_all)]
962 pub fn process_js(
963 &self,
964 handler: &Handler,
965 program: Program,
966 opts: &Options,
967 ) -> Result<TransformOutput, Error> {
968 let loc = self.cm.lookup_char_pos(program.span().lo());
969 let fm = loc.file;
970
971 self.process_js_with_custom_pass(
972 fm,
973 Some(program),
974 handler,
975 opts,
976 SingleThreadedComments::default(),
977 |_| noop_pass(),
978 |_| noop_pass(),
979 )
980 }
981
982 #[tracing::instrument(name = "swc::Compiler::apply_transforms", skip_all)]
983 fn apply_transforms(
984 &self,
985 handler: &Handler,
986 #[allow(unused)] comments: SingleThreadedComments,
987 #[allow(unused)] fm: Arc<SourceFile>,
988 orig: Option<sourcemap::SourceMap>,
989 config: BuiltInput<impl Pass>,
990 ) -> Result<TransformOutput, Error> {
991 self.run(|| {
992 let program = config.program;
993 let is_typescript_syntax = matches!(config.syntax, Syntax::Typescript(..));
994
995 if config.emit_isolated_dts && !is_typescript_syntax {
996 handler.warn(
997 "jsc.experimental.emitIsolatedDts is enabled but the syntax is not TypeScript",
998 );
999 }
1000
1001 let source_map_names = if config.source_maps.enabled() {
1002 let mut v = swc_compiler_base::IdentCollector {
1003 names: Default::default(),
1004 };
1005
1006 program.visit_with(&mut v);
1007
1008 v.names
1009 } else {
1010 Default::default()
1011 };
1012 #[cfg(feature = "isolated-dts")]
1013 let dts_code = if is_typescript_syntax && config.emit_isolated_dts {
1014 use std::cell::RefCell;
1015
1016 use swc_ecma_codegen::to_code_with_comments;
1017 let (leading, trailing) = comments.borrow_all();
1018
1019 let leading = std::rc::Rc::new(RefCell::new(leading.clone()));
1020 let trailing = std::rc::Rc::new(RefCell::new(trailing.clone()));
1021
1022 let comments = SingleThreadedComments::from_leading_and_trailing(leading, trailing);
1023
1024 let mut checker =
1025 FastDts::new(fm.name.clone(), config.unresolved_mark, Default::default());
1026 let mut program = program.clone();
1027
1028 #[cfg(feature = "module")]
1029 if let Some((base, resolver)) = config.resolver {
1030 use swc_ecma_transforms_module::rewriter::import_rewriter;
1031
1032 program.mutate(import_rewriter(base, resolver));
1033 }
1034
1035 let issues = checker.transform(&mut program);
1036
1037 for issue in issues {
1038 handler
1039 .struct_span_err(issue.range.span, &issue.message)
1040 .emit();
1041 }
1042
1043 let dts_code = to_code_with_comments(Some(&comments), &program);
1044 Some(dts_code)
1045 } else {
1046 None
1047 };
1048
1049 let pass = config.pass;
1050 let (program, output) = swc_transform_common::output::capture(|| {
1051 #[cfg(feature = "isolated-dts")]
1052 {
1053 if let Some(dts_code) = dts_code {
1054 use swc_transform_common::output::experimental_emit;
1055 experimental_emit("__swc_isolated_declarations__".into(), dts_code);
1056 }
1057 }
1058
1059 helpers::HELPERS.set(&Helpers::new(config.external_helpers), || {
1060 HANDLER.set(handler, || {
1061 program.apply(pass)
1063 })
1064 })
1065 });
1066
1067 if let Some(comments) = &config.comments {
1068 swc_compiler_base::minify_file_comments(
1069 comments,
1070 config.preserve_comments,
1071 config.output.preserve_annotations.into_bool(),
1072 );
1073 }
1074
1075 self.print(
1076 &program,
1077 PrintArgs {
1078 source_root: config.source_root.as_deref(),
1079 source_file_name: config.source_file_name.as_deref(),
1080 source_map_ignore_list: config.source_map_ignore_list.clone(),
1081 output_path: config.output_path,
1082 inline_sources_content: config.inline_sources_content,
1083 source_map: config.source_maps,
1084 source_map_names: &source_map_names,
1085 orig,
1086 comments: config.comments.as_ref().map(|v| v as _),
1087 emit_source_map_columns: config.emit_source_map_columns,
1088 emit_source_map_scopes: config.emit_source_map_scopes,
1089 preamble: &config.output.preamble,
1090 codegen_config: swc_ecma_codegen::Config::default()
1091 .with_target(config.target)
1092 .with_minify(config.minify)
1093 .with_ascii_only(
1094 config
1095 .output
1096 .charset
1097 .map(|v| matches!(v, OutputCharset::Ascii))
1098 .unwrap_or(false),
1099 )
1100 .with_emit_assert_for_import_attributes(
1101 config.emit_assert_for_import_attributes,
1102 )
1103 .with_inline_script(config.codegen_inline_script),
1104 output: if output.is_empty() {
1105 None
1106 } else {
1107 Some(output)
1108 },
1109 source_map_url: config.output.source_map_url.as_deref(),
1110 },
1111 )
1112 })
1113 }
1114}
1115
1116#[non_exhaustive]
1117#[derive(Clone, Default)]
1118pub struct JsMinifyExtras {
1119 pub mangle_name_cache: Option<Arc<dyn MangleCache>>,
1120}
1121
1122impl JsMinifyExtras {
1123 pub fn with_mangle_name_cache(
1124 mut self,
1125 mangle_name_cache: Option<Arc<dyn MangleCache>>,
1126 ) -> Self {
1127 self.mangle_name_cache = mangle_name_cache;
1128 self
1129 }
1130}
1131
1132fn find_swcrc(path: &Path, root: &Path, root_mode: RootMode) -> Option<PathBuf> {
1133 let mut parent = path.parent();
1134 while let Some(dir) = parent {
1135 let swcrc = dir.join(".swcrc");
1136
1137 if swcrc.exists() {
1138 return Some(swcrc);
1139 }
1140
1141 if dir == root && root_mode == RootMode::Root {
1142 break;
1143 }
1144 parent = dir.parent();
1145 }
1146
1147 None
1148}
1149
1150#[tracing::instrument(skip_all)]
1151fn load_swcrc(path: &Path) -> Result<Rc, Error> {
1152 let content = read_to_string(path).context("failed to read config (.swcrc) file")?;
1153
1154 parse_swcrc(&content)
1155}
1156
1157fn parse_swcrc(s: &str) -> Result<Rc, Error> {
1158 fn convert_json_err(e: serde_json::Error) -> Error {
1159 let line = e.line();
1160 let column = e.column();
1161
1162 let msg = match e.classify() {
1163 Category::Io => "io error",
1164 Category::Syntax => "syntax error",
1165 Category::Data => "unmatched data",
1166 Category::Eof => "unexpected eof",
1167 };
1168 Error::new(e).context(format!(
1169 "failed to deserialize .swcrc (json) file: {msg}: {line}:{column}"
1170 ))
1171 }
1172
1173 let v = parse_to_serde_value(
1174 s.trim_start_matches('\u{feff}'),
1175 &ParseOptions {
1176 allow_comments: true,
1177 allow_trailing_commas: true,
1178 allow_loose_object_property_names: false,
1179 },
1180 )?
1181 .ok_or_else(|| Error::msg("failed to deserialize empty .swcrc (json) file"))?;
1182
1183 if let Ok(rc) = serde_json::from_value(v.clone()) {
1184 return Ok(rc);
1185 }
1186
1187 serde_json::from_value(v)
1188 .map(Rc::Single)
1189 .map_err(convert_json_err)
1190}