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