1use anyhow::{Result, bail};
2use std::collections::VecDeque;
3
4use rustc_hash::{FxHashMap, FxHashSet};
5use std::hash::{Hash, Hasher};
6use std::path::{Path, PathBuf};
7use std::sync::Arc;
8use std::time::Instant;
9
10use crate::args::{CliArgs, ModuleDetection};
11use crate::config::{
12 ResolvedCompilerOptions, TsConfig, checker_target_from_emitter, load_tsconfig,
13 load_tsconfig_with_diagnostics, resolve_compiler_options, resolve_default_lib_files,
14 resolve_lib_files,
15};
16use tsz::binder::BinderOptions;
17use tsz::binder::BinderState;
18use tsz::binder::{SymbolId, SymbolTable, symbol_flags};
19use tsz::checker::TypeCache;
20use tsz::checker::context::LibContext;
21use tsz::checker::diagnostics::{
22 Diagnostic, DiagnosticCategory, DiagnosticRelatedInformation, diagnostic_codes,
23 diagnostic_messages, format_message,
24};
25use tsz::checker::state::CheckerState;
26use tsz::lib_loader::LibFile;
27use tsz::module_resolver::ModuleResolver;
28use tsz::span::Span;
29use tsz_binder::state::BinderStateScopeInputs;
30use tsz_common::common::ModuleKind;
31use crate::driver_emit::{EmitOutputsContext, emit_outputs, normalize_type_roots, write_outputs};
33pub(crate) use crate::driver_emit::{normalize_base_url, normalize_output_dir, normalize_root_dir};
34use crate::driver_resolution::{
35 ModuleResolutionCache, canonicalize_or_owned, collect_export_binding_nodes,
36 collect_import_bindings, collect_module_specifiers, collect_module_specifiers_from_text,
37 collect_star_export_specifiers, collect_type_packages_from_root, default_type_roots, env_flag,
38 resolve_module_specifier, resolve_type_package_entry, resolve_type_package_from_roots,
39};
40use crate::fs::{FileDiscoveryOptions, discover_ts_files, is_js_file};
41use crate::incremental::{BuildInfo, default_build_info_path};
42use rustc_hash::FxHasher;
43#[cfg(test)]
44use std::cell::RefCell;
45use tsz::parallel::{self, BindResult, BoundFile, MergedProgram};
46use tsz::parser::NodeIndex;
47use tsz::parser::ParseDiagnostic;
48use tsz::parser::node::{NodeAccess, NodeArena};
49use tsz::parser::syntax_kind_ext;
50use tsz::scanner::SyntaxKind;
51use tsz_solver::{QueryCache, TypeFormatter, TypeId};
52
53#[derive(Debug, Clone, PartialEq, Eq)]
55pub enum FileInclusionReason {
56 RootFile,
58 IncludePattern(String),
60 ImportedFrom(PathBuf),
62 LibFile,
64 TypeReference(PathBuf),
66 TripleSlashReference(PathBuf),
68}
69
70impl std::fmt::Display for FileInclusionReason {
71 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
72 match self {
73 Self::RootFile => write!(f, "Root file specified"),
74 Self::IncludePattern(pattern) => {
75 write!(f, "Matched by include pattern '{pattern}'")
76 }
77 Self::ImportedFrom(path) => {
78 write!(f, "Imported from '{}'", path.display())
79 }
80 Self::LibFile => write!(f, "Library file"),
81 Self::TypeReference(path) => {
82 write!(f, "Type reference from '{}'", path.display())
83 }
84 Self::TripleSlashReference(path) => {
85 write!(f, "Referenced from '{}'", path.display())
86 }
87 }
88 }
89}
90
91#[derive(Debug, Clone)]
93pub struct FileInfo {
94 pub path: PathBuf,
96 pub reasons: Vec<FileInclusionReason>,
98}
99
100#[derive(Debug, Clone)]
101pub struct CompilationResult {
102 pub diagnostics: Vec<Diagnostic>,
103 pub emitted_files: Vec<PathBuf>,
104 pub files_read: Vec<PathBuf>,
105 pub file_infos: Vec<FileInfo>,
107}
108
109const TYPES_VERSIONS_COMPILER_VERSION_ENV_KEY: &str = "TSZ_TYPES_VERSIONS_COMPILER_VERSION";
110
111#[cfg(test)]
112thread_local! {
113 static TEST_TYPES_VERSIONS_COMPILER_VERSION_OVERRIDE: RefCell<Option<Option<String>>> =
114 const { RefCell::new(None) };
115}
116
117#[cfg(test)]
118struct TestTypesVersionsEnvGuard {
119 previous: Option<Option<String>>,
120}
121
122#[cfg(test)]
123impl Drop for TestTypesVersionsEnvGuard {
124 fn drop(&mut self) {
125 TEST_TYPES_VERSIONS_COMPILER_VERSION_OVERRIDE.with(|slot| {
126 let mut slot = slot.borrow_mut();
127 *slot = self.previous.clone();
128 });
129 }
130}
131
132#[cfg(test)]
133pub(crate) fn with_types_versions_env<T>(value: Option<&str>, f: impl FnOnce() -> T) -> T {
134 let value = value.map(str::to_string);
135 let previous = TEST_TYPES_VERSIONS_COMPILER_VERSION_OVERRIDE.with(|slot| {
136 let mut slot = slot.borrow_mut();
137 let previous = slot.clone();
138 *slot = Some(value);
139 previous
140 });
141 let _guard = TestTypesVersionsEnvGuard { previous };
142 f()
143}
144
145#[cfg(test)]
146fn test_types_versions_compiler_version_override() -> Option<Option<String>> {
147 TEST_TYPES_VERSIONS_COMPILER_VERSION_OVERRIDE.with(|slot| slot.borrow().clone())
148}
149
150fn types_versions_compiler_version_env() -> Option<String> {
151 #[cfg(test)]
152 if let Some(override_value) = test_types_versions_compiler_version_override() {
153 return override_value;
154 }
155 std::env::var(TYPES_VERSIONS_COMPILER_VERSION_ENV_KEY).ok()
156}
157
158#[derive(Default)]
159pub(crate) struct CompilationCache {
160 type_caches: FxHashMap<PathBuf, TypeCache>,
161 bind_cache: FxHashMap<PathBuf, BindCacheEntry>,
162 dependencies: FxHashMap<PathBuf, FxHashSet<PathBuf>>,
163 reverse_dependencies: FxHashMap<PathBuf, FxHashSet<PathBuf>>,
164 diagnostics: FxHashMap<PathBuf, Vec<Diagnostic>>,
165 export_hashes: FxHashMap<PathBuf, u64>,
166 import_symbol_ids: FxHashMap<PathBuf, FxHashMap<PathBuf, Vec<SymbolId>>>,
167 star_export_dependencies: FxHashMap<PathBuf, FxHashSet<PathBuf>>,
168}
169
170struct BindCacheEntry {
171 hash: u64,
172 bind_result: BindResult,
173}
174
175impl CompilationCache {
176 #[cfg(test)]
177 pub(crate) fn len(&self) -> usize {
178 self.type_caches.len()
179 }
180
181 #[cfg(test)]
182 pub(crate) fn bind_len(&self) -> usize {
183 self.bind_cache.len()
184 }
185
186 #[cfg(test)]
187 pub(crate) fn diagnostics_len(&self) -> usize {
188 self.diagnostics.len()
189 }
190
191 #[cfg(test)]
192 pub(crate) fn symbol_cache_len(&self, path: &Path) -> Option<usize> {
193 self.type_caches
194 .get(path)
195 .map(|cache| cache.symbol_types.len())
196 }
197
198 #[cfg(test)]
199 pub(crate) fn node_cache_len(&self, path: &Path) -> Option<usize> {
200 self.type_caches
201 .get(path)
202 .map(|cache| cache.node_types.len())
203 }
204
205 #[cfg(test)]
206 pub(crate) fn invalidate_paths_with_dependents<I>(&mut self, paths: I)
207 where
208 I: IntoIterator<Item = PathBuf>,
209 {
210 let changed: FxHashSet<PathBuf> = paths.into_iter().collect();
211 let affected = self.collect_dependents(changed.iter().cloned());
212 for path in affected {
213 self.type_caches.remove(&path);
214 self.bind_cache.remove(&path);
215 self.diagnostics.remove(&path);
216 self.export_hashes.remove(&path);
217 self.import_symbol_ids.remove(&path);
218 self.star_export_dependencies.remove(&path);
219 }
220 }
221
222 pub(crate) fn invalidate_paths_with_dependents_symbols<I>(&mut self, paths: I)
223 where
224 I: IntoIterator<Item = PathBuf>,
225 {
226 let changed: FxHashSet<PathBuf> = paths.into_iter().collect();
227 let affected = self.collect_dependents(changed.iter().cloned());
228 for path in affected {
229 if changed.contains(&path) {
230 self.type_caches.remove(&path);
231 self.bind_cache.remove(&path);
232 self.diagnostics.remove(&path);
233 self.export_hashes.remove(&path);
234 self.import_symbol_ids.remove(&path);
235 self.star_export_dependencies.remove(&path);
236 continue;
237 }
238
239 self.diagnostics.remove(&path);
240 self.export_hashes.remove(&path);
241
242 let mut roots = Vec::new();
243 if let Some(dep_map) = self.import_symbol_ids.get(&path) {
244 for changed_path in &changed {
245 if let Some(symbols) = dep_map.get(changed_path) {
246 roots.extend(symbols.iter().copied());
247 }
248 }
249 }
250
251 if roots.is_empty() {
252 let has_star_export =
253 self.star_export_dependencies
254 .get(&path)
255 .is_some_and(|deps| {
256 changed
257 .iter()
258 .any(|changed_path| deps.contains(changed_path))
259 });
260 if has_star_export {
261 if let Some(cache) = self.type_caches.get_mut(&path) {
262 cache.node_types.clear();
263 }
264 } else {
265 self.type_caches.remove(&path);
266 }
267 continue;
268 }
269
270 if let Some(cache) = self.type_caches.get_mut(&path) {
271 cache.invalidate_symbols(&roots);
272 }
273 }
274 }
275
276 pub(crate) fn invalidate_paths<I>(&mut self, paths: I)
277 where
278 I: IntoIterator<Item = PathBuf>,
279 {
280 for path in paths {
281 self.type_caches.remove(&path);
282 self.bind_cache.remove(&path);
283 self.diagnostics.remove(&path);
284 self.export_hashes.remove(&path);
285 self.import_symbol_ids.remove(&path);
286 self.star_export_dependencies.remove(&path);
287 }
288 }
289
290 pub(crate) fn clear(&mut self) {
291 self.type_caches.clear();
292 self.bind_cache.clear();
293 self.dependencies.clear();
294 self.reverse_dependencies.clear();
295 self.diagnostics.clear();
296 self.export_hashes.clear();
297 self.import_symbol_ids.clear();
298 self.star_export_dependencies.clear();
299 }
300
301 pub(crate) fn update_dependencies(
302 &mut self,
303 dependencies: FxHashMap<PathBuf, FxHashSet<PathBuf>>,
304 ) {
305 let mut reverse = FxHashMap::default();
306 for (source, deps) in &dependencies {
307 for dep in deps {
308 reverse
309 .entry(dep.clone())
310 .or_insert_with(FxHashSet::default)
311 .insert(source.clone());
312 }
313 }
314 self.dependencies = dependencies;
315 self.reverse_dependencies = reverse;
316 }
317
318 fn collect_dependents<I>(&self, paths: I) -> FxHashSet<PathBuf>
319 where
320 I: IntoIterator<Item = PathBuf>,
321 {
322 let mut pending = VecDeque::new();
323 let mut affected = FxHashSet::default();
324
325 for path in paths {
326 if affected.insert(path.clone()) {
327 pending.push_back(path);
328 }
329 }
330
331 while let Some(path) = pending.pop_front() {
332 let Some(dependents) = self.reverse_dependencies.get(&path) else {
333 continue;
334 };
335 for dependent in dependents {
336 if affected.insert(dependent.clone()) {
337 pending.push_back(dependent.clone());
338 }
339 }
340 }
341
342 affected
343 }
344}
345
346fn compilation_cache_to_build_info(
348 cache: &CompilationCache,
349 root_files: &[PathBuf],
350 base_dir: &Path,
351 options: &ResolvedCompilerOptions,
352) -> BuildInfo {
353 use crate::incremental::{
354 BuildInfoOptions, CachedDiagnostic, CachedRelatedInformation, EmitSignature,
355 FileInfo as IncrementalFileInfo,
356 };
357 use std::collections::BTreeMap;
358
359 let mut file_infos = BTreeMap::new();
360 let mut dependencies = BTreeMap::new();
361 let mut emit_signatures = BTreeMap::new();
362
363 for (path, hash) in &cache.export_hashes {
365 let relative_path: String = path
366 .strip_prefix(base_dir)
367 .unwrap_or(path)
368 .to_string_lossy()
369 .replace('\\', "/");
370
371 let version = format!("{hash:016x}");
373 let signature = Some(format!("{hash:016x}"));
374 file_infos.insert(
375 relative_path.clone(),
376 IncrementalFileInfo {
377 version,
378 signature,
379 affected_files_pending_emit: false,
380 implied_format: None,
381 },
382 );
383
384 if let Some(deps) = cache.dependencies.get(path) {
386 let dep_strs: Vec<String> = deps
387 .iter()
388 .map(|d| {
389 d.strip_prefix(base_dir)
390 .unwrap_or(d)
391 .to_string_lossy()
392 .replace('\\', "/")
393 })
394 .collect();
395 dependencies.insert(relative_path.clone(), dep_strs);
396 }
397
398 emit_signatures.insert(
400 relative_path,
401 EmitSignature {
402 js: None,
403 dts: None,
404 map: None,
405 },
406 );
407 }
408
409 let mut semantic_diagnostics_per_file = BTreeMap::new();
411 for (path, diagnostics) in &cache.diagnostics {
412 let relative_path: String = path
413 .strip_prefix(base_dir)
414 .unwrap_or(path)
415 .to_string_lossy()
416 .replace('\\', "/");
417
418 let cached_diagnostics: Vec<CachedDiagnostic> = diagnostics
419 .iter()
420 .map(|d| {
421 let file_path = Path::new(&d.file);
422 CachedDiagnostic {
423 file: file_path
424 .strip_prefix(base_dir)
425 .unwrap_or(file_path)
426 .to_string_lossy()
427 .replace('\\', "/"),
428 start: d.start,
429 length: d.length,
430 message_text: d.message_text.clone(),
431 category: d.category as u8,
432 code: d.code,
433 related_information: d
434 .related_information
435 .iter()
436 .map(|r| {
437 let rel_file_path = Path::new(&r.file);
438 CachedRelatedInformation {
439 file: rel_file_path
440 .strip_prefix(base_dir)
441 .unwrap_or(rel_file_path)
442 .to_string_lossy()
443 .replace('\\', "/"),
444 start: r.start,
445 length: r.length,
446 message_text: r.message_text.clone(),
447 category: r.category as u8,
448 code: r.code,
449 }
450 })
451 .collect(),
452 }
453 })
454 .collect();
455
456 if !cached_diagnostics.is_empty() {
457 semantic_diagnostics_per_file.insert(relative_path, cached_diagnostics);
458 }
459 }
460
461 let root_files_str: Vec<String> = root_files
463 .iter()
464 .map(|p| {
465 p.strip_prefix(base_dir)
466 .unwrap_or(p)
467 .to_string_lossy()
468 .replace('\\', "/")
469 })
470 .collect();
471
472 let build_options = BuildInfoOptions {
474 target: Some(format!("{:?}", options.checker.target)),
475 module: Some(format!("{:?}", options.printer.module)),
476 declaration: Some(options.emit_declarations),
477 strict: Some(options.checker.strict),
478 };
479
480 BuildInfo {
481 version: crate::incremental::BUILD_INFO_VERSION.to_string(),
482 compiler_version: env!("CARGO_PKG_VERSION").to_string(),
483 root_files: root_files_str,
484 file_infos,
485 dependencies,
486 semantic_diagnostics_per_file,
487 emit_signatures,
488 latest_changed_dts_file: None, options: build_options,
490 build_time: std::time::SystemTime::now()
491 .duration_since(std::time::UNIX_EPOCH)
492 .map(|d| d.as_secs())
493 .unwrap_or(0),
494 }
495}
496
497fn build_info_to_compilation_cache(build_info: &BuildInfo, base_dir: &Path) -> CompilationCache {
499 let mut cache = CompilationCache::default();
500
501 for (path_str, file_info) in &build_info.file_infos {
503 let full_path = base_dir.join(path_str);
504
505 if let Ok(hash) = u64::from_str_radix(&file_info.version, 16) {
507 cache.export_hashes.insert(full_path.clone(), hash);
508 }
509
510 if let Some(deps) = build_info.get_dependencies(path_str) {
512 let mut dep_paths = FxHashSet::default();
513 for dep in deps {
514 let dep_path = base_dir.join(dep);
515 cache
516 .reverse_dependencies
517 .entry(dep_path.clone())
518 .or_default()
519 .insert(full_path.clone());
520 dep_paths.insert(dep_path);
521 }
522 cache.dependencies.insert(full_path, dep_paths);
523 }
524 }
525
526 for (path_str, cached_diagnostics) in &build_info.semantic_diagnostics_per_file {
528 let full_path = base_dir.join(path_str);
529
530 let diagnostics: Vec<Diagnostic> = cached_diagnostics
531 .iter()
532 .map(|cd| Diagnostic {
533 file: full_path.to_string_lossy().into_owned(),
534 start: cd.start,
535 length: cd.length,
536 message_text: cd.message_text.clone(),
537 category: match cd.category {
538 0 => DiagnosticCategory::Warning,
539 1 => DiagnosticCategory::Error,
540 2 => DiagnosticCategory::Suggestion,
541 _ => DiagnosticCategory::Message,
542 },
543 code: cd.code,
544 related_information: cd
545 .related_information
546 .iter()
547 .map(|r| DiagnosticRelatedInformation {
548 file: base_dir.join(&r.file).to_string_lossy().into_owned(),
549 start: r.start,
550 length: r.length,
551 message_text: r.message_text.clone(),
552 category: match r.category {
553 0 => DiagnosticCategory::Warning,
554 1 => DiagnosticCategory::Error,
555 2 => DiagnosticCategory::Suggestion,
556 _ => DiagnosticCategory::Message,
557 },
558 code: r.code,
559 })
560 .collect(),
561 })
562 .collect();
563
564 if !diagnostics.is_empty() {
565 cache.diagnostics.insert(full_path, diagnostics);
566 }
567 }
568
569 cache
570}
571
572fn get_build_info_path(
574 tsconfig_path: Option<&Path>,
575 options: &ResolvedCompilerOptions,
576 base_dir: &Path,
577) -> Option<PathBuf> {
578 if !options.incremental && options.ts_build_info_file.is_none() {
579 return None;
580 }
581
582 if let Some(ref explicit_path) = options.ts_build_info_file {
583 return Some(base_dir.join(explicit_path));
584 }
585
586 let config_path = tsconfig_path?;
588 let out_dir = options.out_dir.as_ref().map(|od| base_dir.join(od));
589 Some(default_build_info_path(config_path, out_dir.as_deref()))
590}
591
592pub fn compile(args: &CliArgs, cwd: &Path) -> Result<CompilationResult> {
593 compile_inner(args, cwd, None, None, None, None)
594}
595
596pub fn compile_project(
598 args: &CliArgs,
599 cwd: &Path,
600 config_path: &Path,
601) -> Result<CompilationResult> {
602 compile_inner(args, cwd, None, None, None, Some(config_path))
603}
604
605pub(crate) fn compile_with_cache(
606 args: &CliArgs,
607 cwd: &Path,
608 cache: &mut CompilationCache,
609) -> Result<CompilationResult> {
610 compile_inner(args, cwd, Some(cache), None, None, None)
611}
612
613pub(crate) fn compile_with_cache_and_changes(
614 args: &CliArgs,
615 cwd: &Path,
616 cache: &mut CompilationCache,
617 changed_paths: &[PathBuf],
618) -> Result<CompilationResult> {
619 let canonical_paths: Vec<PathBuf> = changed_paths
620 .iter()
621 .map(|path| canonicalize_or_owned(path))
622 .collect();
623 let mut old_hashes = FxHashMap::default();
624 for path in &canonical_paths {
625 if let Some(&hash) = cache.export_hashes.get(path) {
626 old_hashes.insert(path.clone(), hash);
627 }
628 }
629
630 cache.invalidate_paths(canonical_paths.iter().cloned());
631 let result = compile_inner(args, cwd, Some(cache), Some(&canonical_paths), None, None)?;
632
633 let exports_changed = canonical_paths
634 .iter()
635 .any(|path| old_hashes.get(path).copied() != cache.export_hashes.get(path).copied());
636 if !exports_changed {
637 return Ok(result);
638 }
639
640 let dependents = if args.assume_changes_only_affect_direct_dependencies {
642 let mut direct_dependents = FxHashSet::default();
644 for path in &canonical_paths {
645 if let Some(deps) = cache.reverse_dependencies.get(path) {
646 direct_dependents.extend(deps.iter().cloned());
647 }
648 }
649 direct_dependents
650 } else {
651 cache.collect_dependents(canonical_paths.iter().cloned())
653 };
654
655 cache.invalidate_paths_with_dependents_symbols(canonical_paths);
656 compile_inner(
657 args,
658 cwd,
659 Some(cache),
660 Some(changed_paths),
661 Some(&dependents),
662 None,
663 )
664}
665
666fn compile_inner(
667 args: &CliArgs,
668 cwd: &Path,
669 mut cache: Option<&mut CompilationCache>,
670 changed_paths: Option<&[PathBuf]>,
671 forced_dirty_paths: Option<&FxHashSet<PathBuf>>,
672 explicit_config_path: Option<&Path>,
673) -> Result<CompilationResult> {
674 let _compile_span = tracing::info_span!("compile", cwd = %cwd.display()).entered();
675 let perf_enabled = std::env::var_os("TSZ_PERF").is_some();
676 let compile_start = Instant::now();
677
678 let perf_log_phase = |phase: &'static str, start: Instant| {
679 if perf_enabled {
680 tracing::info!(
681 target: "wasm::perf",
682 phase,
683 ms = start.elapsed().as_secs_f64() * 1000.0
684 );
685 }
686 };
687
688 let cwd = canonicalize_or_owned(cwd);
689 let tsconfig_path = if let Some(path) = explicit_config_path {
690 Some(path.to_path_buf())
691 } else {
692 match resolve_tsconfig_path(&cwd, args.project.as_deref()) {
693 Ok(path) => path,
694 Err(err) => {
695 return Ok(config_error_result(
696 None,
697 err.to_string(),
698 diagnostic_codes::CANNOT_FIND_A_TSCONFIG_JSON_FILE_AT_THE_SPECIFIED_DIRECTORY,
699 ));
700 }
701 }
702 };
703 let loaded = load_config_with_diagnostics(tsconfig_path.as_deref())?;
704 let config = loaded.config;
705 let config_diagnostics = loaded.diagnostics;
706
707 if config_diagnostics
710 .iter()
711 .any(|d| d.code == diagnostic_codes::INVALID_VALUE_FOR_IGNOREDEPRECATIONS)
712 {
713 return Ok(CompilationResult {
714 diagnostics: config_diagnostics,
715 emitted_files: Vec::new(),
716 files_read: Vec::new(),
717 file_infos: Vec::new(),
718 });
719 }
720
721 let mut resolved = resolve_compiler_options(
722 config
723 .as_ref()
724 .and_then(|cfg| cfg.compiler_options.as_ref()),
725 )?;
726 apply_cli_overrides(&mut resolved, args)?;
727
728 if loaded.suppress_excess_property_errors {
730 resolved.checker.suppress_excess_property_errors = true;
731 }
732 if loaded.suppress_implicit_any_index_errors {
733 resolved.checker.suppress_implicit_any_index_errors = true;
734 }
735 if config.is_none()
736 && args.module.is_none()
737 && matches!(resolved.printer.module, ModuleKind::None)
738 {
739 let default_module = if resolved.printer.target.supports_es2015() {
742 ModuleKind::ES2015
743 } else {
744 ModuleKind::CommonJS
745 };
746 resolved.printer.module = default_module;
747 resolved.checker.module = default_module;
748 }
749
750 if let Some(diag) = check_module_resolution_compatibility(&resolved, tsconfig_path.as_deref()) {
751 return Ok(CompilationResult {
752 diagnostics: vec![diag],
753 emitted_files: Vec::new(),
754 files_read: Vec::new(),
755 file_infos: Vec::new(),
756 });
757 }
758
759 let base_dir = config_base_dir(&cwd, tsconfig_path.as_deref());
760 let base_dir = canonicalize_or_owned(&base_dir);
761 let root_dir = normalize_root_dir(&base_dir, resolved.root_dir.clone());
762 let out_dir = normalize_output_dir(&base_dir, resolved.out_dir.clone());
763 let declaration_dir = normalize_output_dir(&base_dir, resolved.declaration_dir.clone());
764 let base_url = normalize_base_url(&base_dir, resolved.base_url.clone());
765 resolved.base_url = base_url;
766 resolved.type_roots = normalize_type_roots(&base_dir, resolved.type_roots.clone());
767
768 let discovery = build_discovery_options(
769 args,
770 &base_dir,
771 tsconfig_path.as_deref(),
772 config.as_ref(),
773 out_dir.as_deref(),
774 &resolved,
775 )?;
776 let mut file_paths = discover_ts_files(&discovery)?;
777
778 let mut should_save_build_info = false;
780
781 let mut local_cache: Option<CompilationCache> = None;
784
785 if cache.is_none() && (resolved.incremental || resolved.ts_build_info_file.is_some()) {
787 let tsconfig_path_ref = tsconfig_path.as_deref();
788 if let Some(build_info_path) = get_build_info_path(tsconfig_path_ref, &resolved, &base_dir)
789 {
790 if build_info_path.exists() {
791 match BuildInfo::load(&build_info_path) {
792 Ok(Some(build_info)) => {
793 local_cache = Some(build_info_to_compilation_cache(&build_info, &base_dir));
795 tracing::info!("Loaded BuildInfo from: {}", build_info_path.display());
796 }
797 Ok(None) => {
798 tracing::info!(
799 "BuildInfo at {} is outdated or incompatible, starting fresh",
800 build_info_path.display()
801 );
802 }
803 Err(e) => {
804 tracing::warn!(
805 "Failed to load BuildInfo from {}: {}, starting fresh",
806 build_info_path.display(),
807 e
808 );
809 }
810 }
811 } else {
812 local_cache = Some(CompilationCache::default());
814 }
815 should_save_build_info = true;
816 }
817 }
818
819 if file_paths.is_empty() {
822 let config_name = tsconfig_path
825 .as_ref()
826 .map(|path| path.to_string_lossy().to_string())
827 .unwrap_or_else(|| "tsconfig.json".to_string());
828 let include_str = discovery
829 .include
830 .as_ref()
831 .filter(|v| !v.is_empty())
832 .map(|v| {
833 v.iter()
834 .map(|s| format!("\"{s}\""))
835 .collect::<Vec<_>>()
836 .join(",")
837 })
838 .unwrap_or_default();
839 let exclude_str = discovery
840 .exclude
841 .as_ref()
842 .filter(|v| !v.is_empty())
843 .map(|v| {
844 v.iter()
845 .map(|s| format!("\"{s}\""))
846 .collect::<Vec<_>>()
847 .join(",")
848 })
849 .unwrap_or_default();
850 let message = format!(
851 "No inputs were found in config file '{config_name}'. Specified 'include' paths were '[{include_str}]' and 'exclude' paths were '[{exclude_str}]'."
852 );
853 return Ok(CompilationResult {
854 diagnostics: vec![Diagnostic::error(String::new(), 0, 0, message, 18003)],
856 emitted_files: Vec::new(),
857 files_read: Vec::new(),
858 file_infos: Vec::new(),
859 });
860 }
861
862 let (type_files, unresolved_types) = collect_type_root_files(&base_dir, &resolved);
863
864 if !type_files.is_empty() {
869 let mut merged = std::collections::BTreeSet::new();
870 merged.extend(file_paths);
871 merged.extend(type_files);
872 file_paths = merged.into_iter().collect();
873 }
874
875 let changed_set = changed_paths.map(|paths| {
876 paths
877 .iter()
878 .map(|path| canonicalize_or_owned(path))
879 .collect::<FxHashSet<_>>()
880 });
881
882 let local_cache_ref = local_cache.as_mut();
885 let mut effective_cache = local_cache_ref.or(cache.as_deref_mut());
886
887 let read_sources_start = Instant::now();
888 let SourceReadResult {
889 sources: all_sources,
890 dependencies,
891 type_reference_errors,
892 } = {
893 read_source_files(
894 &file_paths,
895 &base_dir,
896 &resolved,
897 effective_cache.as_deref(),
898 changed_set.as_ref(),
899 )?
900 };
901 perf_log_phase("read_sources", read_sources_start);
902
903 if let Some(ref mut c) = effective_cache {
905 c.update_dependencies(dependencies);
906 }
907
908 let mut type_file_diagnostics: Vec<Diagnostic> = Vec::new();
910 for (path, type_name) in type_reference_errors {
911 let file_name = path.to_string_lossy().into_owned();
912 type_file_diagnostics.push(Diagnostic::error(
913 file_name,
914 0,
915 0,
916 format!("Cannot find type definition file for '{type_name}'."),
917 diagnostic_codes::CANNOT_FIND_TYPE_DEFINITION_FILE_FOR,
918 ));
919 }
920 for type_name in &unresolved_types {
922 type_file_diagnostics.push(Diagnostic::error(
923 String::new(),
924 0,
925 0,
926 format!("Cannot find type definition file for '{type_name}'."),
927 diagnostic_codes::CANNOT_FIND_TYPE_DEFINITION_FILE_FOR,
928 ));
929 }
930
931 let mut binary_file_diagnostics: Vec<Diagnostic> = Vec::new();
932 let mut binary_file_names: FxHashSet<String> = FxHashSet::default();
933 let mut sources: Vec<SourceEntry> = Vec::with_capacity(all_sources.len());
934 for source in all_sources {
935 if source.is_binary {
936 let file_name = source.path.to_string_lossy().into_owned();
941 binary_file_names.insert(file_name.clone());
942 binary_file_diagnostics.push(Diagnostic::error(
943 file_name,
944 0,
945 0,
946 "File appears to be binary.".to_string(),
947 diagnostic_codes::FILE_APPEARS_TO_BE_BINARY,
948 ));
949 }
950 sources.push(source);
951 }
952
953 let mut files_read: Vec<PathBuf> = sources.iter().map(|s| s.path.clone()).collect();
955 files_read.sort();
956
957 let file_infos = build_file_infos(&sources, &file_paths, args, config.as_ref(), &base_dir);
959
960 let disable_default_libs = resolved.lib_is_default && sources_have_no_default_lib(&sources);
961 let _no_types_and_symbols =
965 resolved.checker.no_types_and_symbols || sources_have_no_types_and_symbols(&sources);
966 resolved.checker.no_types_and_symbols = _no_types_and_symbols;
967 let lib_paths: Vec<PathBuf> =
968 if (resolved.checker.no_lib && resolved.lib_is_default) || disable_default_libs {
969 Vec::new()
970 } else {
971 resolved.lib_files.clone()
972 };
973 let lib_path_refs: Vec<&Path> = lib_paths.iter().map(PathBuf::as_path).collect();
974 let load_libs_start = Instant::now();
978 let lib_files: Vec<Arc<LibFile>> = parallel::load_lib_files_for_binding_strict(&lib_path_refs)?;
979 perf_log_phase("load_libs", load_libs_start);
980
981 let build_program_start = Instant::now();
982 let (program, dirty_paths) = if let Some(ref mut c) = effective_cache {
983 let result = build_program_with_cache(sources, c, &lib_files);
984 (result.program, Some(result.dirty_paths))
985 } else {
986 let compile_inputs: Vec<(String, String)> = sources
987 .into_iter()
988 .map(|source| {
989 let text = source.text.unwrap_or_else(|| {
990 String::new()
993 });
994 (source.path.to_string_lossy().into_owned(), text)
995 })
996 .collect();
997 let bind_results = parallel::parse_and_bind_parallel_with_libs(compile_inputs, &lib_files);
998 (parallel::merge_bind_results(bind_results), None)
999 };
1000 perf_log_phase("build_program", build_program_start);
1001
1002 if let Some(ref mut c) = effective_cache {
1004 update_import_symbol_ids(&program, &resolved, &base_dir, c);
1005 }
1006
1007 let build_lib_contexts_start = Instant::now();
1009 let lib_contexts = if resolved.no_check {
1010 Vec::new() } else {
1012 load_lib_files_for_contexts(&lib_files)
1013 };
1014 perf_log_phase("build_lib_contexts", build_lib_contexts_start);
1015
1016 let collect_diagnostics_start = Instant::now();
1017 let mut diagnostics: Vec<Diagnostic> = collect_diagnostics(
1018 &program,
1019 &resolved,
1020 &base_dir,
1021 effective_cache,
1022 &lib_contexts,
1023 );
1024 perf_log_phase("collect_diagnostics", collect_diagnostics_start);
1025
1026 let empty_type_caches = FxHashMap::default();
1029 let type_caches_ref: &FxHashMap<_, _> = local_cache
1030 .as_ref()
1031 .map(|c| &c.type_caches)
1032 .or_else(|| cache.as_ref().map(|c| &c.type_caches))
1033 .unwrap_or(&empty_type_caches);
1034 if !binary_file_names.is_empty() {
1038 diagnostics.retain(|d| !binary_file_names.contains(&d.file));
1039 }
1040 diagnostics.extend(config_diagnostics);
1041 diagnostics.extend(binary_file_diagnostics);
1042 diagnostics.extend(type_file_diagnostics);
1043 diagnostics.sort_by(|left, right| {
1044 left.file
1045 .cmp(&right.file)
1046 .then(left.start.cmp(&right.start))
1047 .then(left.code.cmp(&right.code))
1048 });
1049
1050 let has_error = diagnostics
1051 .iter()
1052 .any(|diag| diag.category == DiagnosticCategory::Error);
1053 let should_emit = !(resolved.no_emit || (resolved.no_emit_on_error && has_error));
1054
1055 let mut dirty_paths = dirty_paths;
1056 if let Some(forced) = forced_dirty_paths {
1057 match &mut dirty_paths {
1058 Some(existing) => {
1059 existing.extend(forced.iter().cloned());
1060 }
1061 None => {
1062 dirty_paths = Some(forced.clone());
1063 }
1064 }
1065 }
1066
1067 let emit_outputs_start = Instant::now();
1068 let emitted_files = if !should_emit {
1069 Vec::new()
1070 } else {
1071 let outputs = emit_outputs(EmitOutputsContext {
1072 program: &program,
1073 options: &resolved,
1074 base_dir: &base_dir,
1075 root_dir: root_dir.as_deref(),
1076 out_dir: out_dir.as_deref(),
1077 declaration_dir: declaration_dir.as_deref(),
1078 dirty_paths: dirty_paths.as_ref(),
1079 type_caches: type_caches_ref,
1080 })?;
1081 write_outputs(&outputs)?
1082 };
1083 perf_log_phase("emit_outputs", emit_outputs_start);
1084
1085 let latest_changed_dts_file = if !emitted_files.is_empty() {
1087 find_latest_dts_file(&emitted_files, &base_dir)
1088 } else {
1089 None
1090 };
1091
1092 if should_save_build_info && !has_error {
1094 let tsconfig_path_ref = tsconfig_path.as_deref();
1095 if let Some(build_info_path) = get_build_info_path(tsconfig_path_ref, &resolved, &base_dir)
1096 {
1097 let mut build_info = if let Some(ref lc) = local_cache {
1100 compilation_cache_to_build_info(lc, &file_paths, &base_dir, &resolved)
1101 } else {
1102 BuildInfo {
1104 version: crate::incremental::BUILD_INFO_VERSION.to_string(),
1105 compiler_version: env!("CARGO_PKG_VERSION").to_string(),
1106 root_files: file_paths
1107 .iter()
1108 .map(|p| {
1109 p.strip_prefix(&base_dir)
1110 .unwrap_or(p)
1111 .to_string_lossy()
1112 .replace('\\', "/")
1113 })
1114 .collect(),
1115 ..Default::default()
1116 }
1117 };
1118
1119 build_info.latest_changed_dts_file = latest_changed_dts_file;
1121
1122 if let Err(e) = build_info.save(&build_info_path) {
1123 tracing::warn!(
1124 "Failed to save BuildInfo to {}: {}",
1125 build_info_path.display(),
1126 e
1127 );
1128 } else {
1129 tracing::info!("Saved BuildInfo to: {}", build_info_path.display());
1130 }
1131 }
1132 }
1133
1134 if perf_enabled {
1135 tracing::info!(
1136 target: "wasm::perf",
1137 phase = "compile_total",
1138 ms = compile_start.elapsed().as_secs_f64() * 1000.0,
1139 files = file_paths.len(),
1140 libs = lib_files.len(),
1141 diagnostics = diagnostics.len(),
1142 emitted = emitted_files.len(),
1143 no_check = resolved.no_check
1144 );
1145 }
1146
1147 Ok(CompilationResult {
1148 diagnostics,
1149 emitted_files,
1150 files_read,
1151 file_infos,
1152 })
1153}
1154
1155fn config_error_result(file_path: Option<&Path>, message: String, code: u32) -> CompilationResult {
1156 let file = file_path
1157 .map(|p| p.display().to_string())
1158 .unwrap_or_default();
1159 CompilationResult {
1160 diagnostics: vec![Diagnostic::error(file, 0, 0, message, code)],
1161 emitted_files: Vec::new(),
1162 files_read: Vec::new(),
1163 file_infos: Vec::new(),
1164 }
1165}
1166
1167fn check_module_resolution_compatibility(
1168 resolved: &ResolvedCompilerOptions,
1169 tsconfig_path: Option<&Path>,
1170) -> Option<Diagnostic> {
1171 use tsz::config::ModuleResolutionKind;
1172 use tsz_common::common::ModuleKind;
1173
1174 let module_resolution = resolved.module_resolution?;
1175 let required = match module_resolution {
1176 ModuleResolutionKind::Node16 => ModuleKind::Node16,
1177 ModuleResolutionKind::NodeNext => ModuleKind::NodeNext,
1178 _ => return None,
1179 };
1180
1181 if resolved.printer.module == required {
1182 return None;
1183 }
1184
1185 let required_str = match required {
1186 ModuleKind::NodeNext => "NodeNext",
1187 _ => "Node16",
1188 };
1189 let resolution_str = match module_resolution {
1190 ModuleResolutionKind::NodeNext => "NodeNext",
1191 _ => "Node16",
1192 };
1193
1194 let message = format_message(
1195 diagnostic_messages::OPTION_MODULE_MUST_BE_SET_TO_WHEN_OPTION_MODULERESOLUTION_IS_SET_TO,
1196 &[required_str, resolution_str],
1197 );
1198 let file = tsconfig_path
1199 .map(|p| p.display().to_string())
1200 .unwrap_or_default();
1201 Some(Diagnostic::error(
1202 file,
1203 0,
1204 0,
1205 message,
1206 diagnostic_codes::OPTION_MODULE_MUST_BE_SET_TO_WHEN_OPTION_MODULERESOLUTION_IS_SET_TO,
1207 ))
1208}
1209
1210fn build_file_infos(
1212 sources: &[SourceEntry],
1213 root_file_paths: &[PathBuf],
1214 args: &CliArgs,
1215 config: Option<&crate::config::TsConfig>,
1216 _base_dir: &Path,
1217) -> Vec<FileInfo> {
1218 let root_set: FxHashSet<_> = root_file_paths.iter().collect();
1219 let cli_files: FxHashSet<_> = args.files.iter().collect();
1220
1221 let include_patterns = config
1223 .and_then(|c| c.include.as_ref())
1224 .map_or_else(|| "**/*".to_string(), |patterns| patterns.join(", "));
1225
1226 sources
1227 .iter()
1228 .map(|source| {
1229 let mut reasons = Vec::new();
1230
1231 if cli_files.iter().any(|f| source.path.ends_with(f)) {
1233 reasons.push(FileInclusionReason::RootFile);
1234 }
1235 else if is_lib_file(&source.path) {
1237 reasons.push(FileInclusionReason::LibFile);
1238 }
1239 else if root_set.contains(&source.path) {
1241 reasons.push(FileInclusionReason::IncludePattern(
1242 include_patterns.clone(),
1243 ));
1244 }
1245 else {
1247 reasons.push(FileInclusionReason::ImportedFrom(PathBuf::from("<import>")));
1248 }
1249
1250 FileInfo {
1251 path: source.path.clone(),
1252 reasons,
1253 }
1254 })
1255 .collect()
1256}
1257
1258fn is_lib_file(path: &Path) -> bool {
1260 let file_name = path.file_name().and_then(|n| n.to_str()).unwrap_or("");
1261
1262 file_name.starts_with("lib.") && file_name.ends_with(".d.ts")
1263}
1264
1265struct SourceMeta {
1266 path: PathBuf,
1267 file_name: String,
1268 hash: u64,
1269 cached_ok: bool,
1270}
1271
1272struct BuildProgramResult {
1273 program: MergedProgram,
1274 dirty_paths: FxHashSet<PathBuf>,
1275}
1276
1277fn build_program_with_cache(
1278 sources: Vec<SourceEntry>,
1279 cache: &mut CompilationCache,
1280 lib_files: &[Arc<LibFile>],
1281) -> BuildProgramResult {
1282 let mut meta = Vec::with_capacity(sources.len());
1283 let mut to_parse = Vec::new();
1284 let mut dirty_paths = FxHashSet::default();
1285
1286 for source in sources {
1287 let file_name = source.path.to_string_lossy().into_owned();
1288 let (hash, cached_ok) = match source.text {
1289 Some(text) => {
1290 let hash = hash_text(&text);
1291 let cached_ok = cache
1292 .bind_cache
1293 .get(&source.path)
1294 .is_some_and(|entry| entry.hash == hash);
1295 if !cached_ok {
1296 dirty_paths.insert(source.path.clone());
1297 to_parse.push((file_name.clone(), text));
1298 }
1299 (hash, cached_ok)
1300 }
1301 None => {
1302 (0, false)
1306 }
1307 };
1308
1309 meta.push(SourceMeta {
1310 path: source.path,
1311 file_name,
1312 hash,
1313 cached_ok,
1314 });
1315 }
1316
1317 let parsed_results = if to_parse.is_empty() {
1318 Vec::new()
1319 } else {
1320 parallel::parse_and_bind_parallel_with_libs(to_parse, lib_files)
1325 };
1326
1327 let mut parsed_map: FxHashMap<String, BindResult> = parsed_results
1328 .into_iter()
1329 .map(|result| (result.file_name.clone(), result))
1330 .collect();
1331
1332 for entry in &meta {
1333 if entry.cached_ok {
1334 continue;
1335 }
1336
1337 let result = match parsed_map.remove(&entry.file_name) {
1338 Some(r) => r,
1339 None => {
1340 BindResult {
1344 file_name: entry.file_name.clone(),
1345 source_file: NodeIndex::NONE, arena: std::sync::Arc::new(NodeArena::new()),
1347 symbols: Default::default(),
1348 file_locals: Default::default(),
1349 declared_modules: Default::default(),
1350 module_exports: Default::default(),
1351 node_symbols: Default::default(),
1352 symbol_arenas: Default::default(),
1353 declaration_arenas: Default::default(),
1354 scopes: Vec::new(),
1355 node_scope_ids: Default::default(),
1356 parse_diagnostics: Vec::new(),
1357 shorthand_ambient_modules: Default::default(),
1358 global_augmentations: Default::default(),
1359 module_augmentations: Default::default(),
1360 reexports: Default::default(),
1361 wildcard_reexports: Default::default(),
1362 lib_binders: Vec::new(),
1363 lib_symbol_ids: Default::default(),
1364 lib_symbol_reverse_remap: Default::default(),
1365 flow_nodes: Default::default(),
1366 node_flow: Default::default(),
1367 switch_clause_to_switch: Default::default(),
1368 is_external_module: false, expando_properties: Default::default(),
1370 }
1371 }
1372 };
1373 cache.bind_cache.insert(
1374 entry.path.clone(),
1375 BindCacheEntry {
1376 hash: entry.hash,
1377 bind_result: result,
1378 },
1379 );
1380 }
1381
1382 let mut current_paths: FxHashSet<PathBuf> =
1383 FxHashSet::with_capacity_and_hasher(meta.len(), Default::default());
1384 for entry in &meta {
1385 current_paths.insert(entry.path.clone());
1386 }
1387 cache
1388 .bind_cache
1389 .retain(|path, _| current_paths.contains(path));
1390
1391 let mut ordered = Vec::with_capacity(meta.len());
1392 for entry in &meta {
1393 let Some(cached) = cache.bind_cache.get(&entry.path) else {
1394 continue;
1395 };
1396 ordered.push(&cached.bind_result);
1397 }
1398
1399 BuildProgramResult {
1400 program: parallel::merge_bind_results_ref(&ordered),
1401 dirty_paths,
1402 }
1403}
1404
1405fn update_import_symbol_ids(
1406 program: &MergedProgram,
1407 options: &ResolvedCompilerOptions,
1408 base_dir: &Path,
1409 cache: &mut CompilationCache,
1410) {
1411 let mut resolution_cache = ModuleResolutionCache::default();
1412 let mut import_symbol_ids: FxHashMap<PathBuf, FxHashMap<PathBuf, Vec<SymbolId>>> =
1413 FxHashMap::default();
1414 let mut star_export_dependencies: FxHashMap<PathBuf, FxHashSet<PathBuf>> = FxHashMap::default();
1415
1416 let known_files: FxHashSet<PathBuf> = program
1418 .files
1419 .iter()
1420 .map(|f| PathBuf::from(&f.file_name))
1421 .collect();
1422
1423 for (file_idx, file) in program.files.iter().enumerate() {
1424 let file_path = PathBuf::from(&file.file_name);
1425 let mut by_dep: FxHashMap<PathBuf, Vec<SymbolId>> = FxHashMap::default();
1426 let mut star_exports: FxHashSet<PathBuf> = FxHashSet::default();
1427 for (specifier, local_names) in collect_import_bindings(&file.arena, file.source_file) {
1428 let resolved = resolve_module_specifier(
1429 Path::new(&file.file_name),
1430 &specifier,
1431 options,
1432 base_dir,
1433 &mut resolution_cache,
1434 &known_files,
1435 );
1436 let Some(resolved) = resolved else {
1437 continue;
1438 };
1439 let canonical = canonicalize_or_owned(&resolved);
1440 let entry = by_dep.entry(canonical).or_default();
1441 if let Some(file_locals) = program.file_locals.get(file_idx) {
1442 for name in local_names {
1443 if let Some(sym_id) = file_locals.get(&name) {
1444 entry.push(sym_id);
1445 }
1446 }
1447 }
1448 }
1449 for (specifier, binding_nodes) in
1450 collect_export_binding_nodes(&file.arena, file.source_file)
1451 {
1452 let resolved = resolve_module_specifier(
1453 Path::new(&file.file_name),
1454 &specifier,
1455 options,
1456 base_dir,
1457 &mut resolution_cache,
1458 &known_files,
1459 );
1460 let Some(resolved) = resolved else {
1461 continue;
1462 };
1463 let canonical = canonicalize_or_owned(&resolved);
1464 let entry = by_dep.entry(canonical).or_default();
1465 for node_idx in binding_nodes {
1466 if let Some(sym_id) = file.node_symbols.get(&node_idx.0).copied() {
1467 entry.push(sym_id);
1468 }
1469 }
1470 }
1471 for specifier in collect_star_export_specifiers(&file.arena, file.source_file) {
1472 let resolved = resolve_module_specifier(
1473 Path::new(&file.file_name),
1474 &specifier,
1475 options,
1476 base_dir,
1477 &mut resolution_cache,
1478 &known_files,
1479 );
1480 let Some(resolved) = resolved else {
1481 continue;
1482 };
1483 let canonical = canonicalize_or_owned(&resolved);
1484 star_exports.insert(canonical);
1485 }
1486 for symbols in by_dep.values_mut() {
1487 symbols.sort_by_key(|sym| sym.0);
1488 symbols.dedup();
1489 }
1490 if !star_exports.is_empty() {
1491 star_export_dependencies.insert(file_path.clone(), star_exports);
1492 }
1493 import_symbol_ids.insert(file_path, by_dep);
1494 }
1495
1496 cache.import_symbol_ids = import_symbol_ids;
1497 cache.star_export_dependencies = star_export_dependencies;
1498}
1499
1500fn hash_text(text: &str) -> u64 {
1501 let mut hasher = FxHasher::default();
1502 text.hash(&mut hasher);
1503 hasher.finish()
1504}
1505
1506#[path = "driver_sources.rs"]
1507mod driver_sources;
1508#[cfg(test)]
1509pub(crate) use driver_sources::has_no_types_and_symbols_directive;
1510pub use driver_sources::{FileReadResult, read_source_file};
1511use driver_sources::{
1512 SourceEntry, SourceReadResult, build_discovery_options, collect_type_root_files,
1513 read_source_files, sources_have_no_default_lib, sources_have_no_types_and_symbols,
1514};
1515pub(crate) use driver_sources::{
1516 config_base_dir, load_config, load_config_with_diagnostics, resolve_tsconfig_path,
1517};
1518
1519#[path = "driver_check.rs"]
1520mod driver_check;
1521use driver_check::{collect_diagnostics, load_lib_files_for_contexts};
1522
1523pub fn apply_cli_overrides(options: &mut ResolvedCompilerOptions, args: &CliArgs) -> Result<()> {
1524 if let Some(target) = args.target {
1525 options.printer.target = target.to_script_target();
1526 options.checker.target = checker_target_from_emitter(options.printer.target);
1527 }
1528 if let Some(module) = args.module {
1529 options.printer.module = module.to_module_kind();
1530 options.checker.module = module.to_module_kind();
1531 options.checker.module_explicitly_set = true;
1532 }
1533 if let Some(module_resolution) = args.module_resolution {
1534 options.module_resolution = Some(module_resolution.to_module_resolution_kind());
1535 }
1536 if let Some(resolve_package_json_exports) = args.resolve_package_json_exports {
1537 options.resolve_package_json_exports = resolve_package_json_exports;
1538 }
1539 if let Some(resolve_package_json_imports) = args.resolve_package_json_imports {
1540 options.resolve_package_json_imports = resolve_package_json_imports;
1541 }
1542 if let Some(module_suffixes) = args.module_suffixes.as_ref() {
1543 options.module_suffixes = module_suffixes.clone();
1544 }
1545 if args.resolve_json_module {
1546 options.resolve_json_module = true;
1547 }
1548 if args.allow_arbitrary_extensions {
1549 options.allow_arbitrary_extensions = true;
1550 }
1551 if args.allow_importing_ts_extensions {
1552 options.allow_importing_ts_extensions = true;
1553 }
1554 if let Some(use_define_for_class_fields) = args.use_define_for_class_fields {
1555 options.printer.use_define_for_class_fields = use_define_for_class_fields;
1556 } else {
1557 options.printer.use_define_for_class_fields =
1559 (options.printer.target as u32) >= (tsz::emitter::ScriptTarget::ES2022 as u32);
1560 }
1561 if args.rewrite_relative_import_extensions {
1562 options.rewrite_relative_import_extensions = true;
1563 }
1564 if let Some(custom_conditions) = args.custom_conditions.as_ref() {
1565 options.custom_conditions = custom_conditions.clone();
1566 }
1567 if let Some(out_dir) = args.out_dir.as_ref() {
1568 options.out_dir = Some(out_dir.clone());
1569 }
1570 if let Some(root_dir) = args.root_dir.as_ref() {
1571 options.root_dir = Some(root_dir.clone());
1572 }
1573 if args.declaration {
1574 options.emit_declarations = true;
1575 }
1576 if args.declaration_map {
1577 options.declaration_map = true;
1578 }
1579 if args.source_map {
1580 options.source_map = true;
1581 }
1582 if let Some(out_file) = args.out_file.as_ref() {
1583 options.out_file = Some(out_file.clone());
1584 }
1585 if let Some(ts_build_info_file) = args.ts_build_info_file.as_ref() {
1586 options.ts_build_info_file = Some(ts_build_info_file.clone());
1587 }
1588 if args.incremental {
1589 options.incremental = true;
1590 }
1591 if args.import_helpers {
1592 options.import_helpers = true;
1593 }
1594 if args.strict {
1595 options.checker.strict = true;
1596 options.checker.no_implicit_any = true;
1598 options.checker.no_implicit_returns = true;
1599 options.checker.strict_null_checks = true;
1600 options.checker.strict_function_types = true;
1601 options.checker.strict_bind_call_apply = true;
1602 options.checker.strict_property_initialization = true;
1603 options.checker.no_implicit_this = true;
1604 options.checker.use_unknown_in_catch_variables = true;
1605 options.checker.always_strict = true;
1606 options.printer.always_strict = true;
1607 }
1608 if let Some(val) = args.strict_null_checks {
1610 options.checker.strict_null_checks = val;
1611 }
1612 if let Some(val) = args.strict_function_types {
1613 options.checker.strict_function_types = val;
1614 }
1615 if let Some(val) = args.strict_property_initialization {
1616 options.checker.strict_property_initialization = val;
1617 }
1618 if let Some(val) = args.strict_bind_call_apply {
1619 options.checker.strict_bind_call_apply = val;
1620 }
1621 if let Some(val) = args.no_implicit_this {
1622 options.checker.no_implicit_this = val;
1623 }
1624 if let Some(val) = args.no_implicit_any {
1625 options.checker.no_implicit_any = val;
1626 }
1627 if let Some(val) = args.use_unknown_in_catch_variables {
1628 options.checker.use_unknown_in_catch_variables = val;
1629 }
1630 if args.no_unchecked_indexed_access {
1631 options.checker.no_unchecked_indexed_access = true;
1632 }
1633 if args.no_implicit_returns {
1634 options.checker.no_implicit_returns = true;
1635 }
1636 if let Some(val) = args.always_strict {
1637 options.checker.always_strict = val;
1638 options.printer.always_strict = val;
1639 }
1640 if let Some(val) = args.allow_unreachable_code {
1641 options.checker.allow_unreachable_code = Some(val);
1642 }
1643 if args.sound {
1644 options.checker.sound_mode = true;
1645 }
1646 if args.experimental_decorators {
1647 options.checker.experimental_decorators = true;
1648 options.printer.legacy_decorators = true;
1649 }
1650 if args.no_unused_locals {
1651 options.checker.no_unused_locals = true;
1652 }
1653 if args.no_unused_parameters {
1654 options.checker.no_unused_parameters = true;
1655 }
1656 if args.no_implicit_override {
1657 options.checker.no_implicit_override = true;
1658 }
1659 if args.es_module_interop {
1660 options.es_module_interop = true;
1661 options.checker.es_module_interop = true;
1662 options.printer.es_module_interop = true;
1663 options.allow_synthetic_default_imports = true;
1665 options.checker.allow_synthetic_default_imports = true;
1666 }
1667 if args.no_emit {
1668 options.no_emit = true;
1669 }
1670 if args.no_resolve {
1671 options.no_resolve = true;
1672 options.checker.no_resolve = true;
1673 }
1674 if args.no_check {
1675 options.no_check = true;
1676 }
1677 if args.skip_lib_check {
1678 options.skip_lib_check = true;
1679 }
1680 if args.allow_js {
1681 options.allow_js = true;
1682 }
1683 if args.check_js {
1684 options.check_js = true;
1685 }
1686 if let Some(version) = args.types_versions_compiler_version.as_ref() {
1687 options.types_versions_compiler_version = Some(version.clone());
1688 } else if let Some(version) = types_versions_compiler_version_env() {
1689 let version = version.trim();
1690 if !version.is_empty() {
1691 options.types_versions_compiler_version = Some(version.to_string());
1692 }
1693 }
1694 if let Some(lib_list) = args.lib.as_ref() {
1695 options.lib_files = resolve_lib_files(lib_list)?;
1696 options.lib_is_default = false;
1697 }
1698 if args.no_lib {
1699 options.checker.no_lib = true;
1700 options.lib_files.clear();
1701 options.lib_is_default = false;
1702 }
1703 if args.downlevel_iteration {
1704 options.printer.downlevel_iteration = true;
1705 }
1706 if args.no_emit_helpers {
1707 options.printer.no_emit_helpers = true;
1708 }
1709 if let Some(ModuleDetection::Force) = args.module_detection {
1710 options.printer.module_detection_force = true;
1711 }
1712 if args.target.is_some() && options.lib_is_default && !options.checker.no_lib {
1713 options.lib_files = resolve_default_lib_files(options.printer.target)?;
1714 }
1715
1716 if args.suppress_excess_property_errors {
1718 options.checker.suppress_excess_property_errors = true;
1719 }
1720 if args.suppress_implicit_any_index_errors {
1721 options.checker.suppress_implicit_any_index_errors = true;
1722 }
1723
1724 Ok(())
1725}
1726
1727fn find_latest_dts_file(emitted_files: &[PathBuf], base_dir: &Path) -> Option<String> {
1730 use std::collections::BTreeMap;
1731
1732 let mut dts_files_with_times: BTreeMap<std::time::SystemTime, PathBuf> = BTreeMap::new();
1733
1734 for path in emitted_files {
1736 if path.extension().and_then(|s| s.to_str()) == Some("d.ts")
1737 && let Ok(metadata) = std::fs::metadata(path)
1738 && let Ok(modified) = metadata.modified()
1739 {
1740 dts_files_with_times.insert(modified, path.clone());
1741 }
1742 }
1743
1744 if let Some((_, latest_path)) = dts_files_with_times.last_key_value() {
1746 let relative = latest_path
1748 .strip_prefix(base_dir)
1749 .unwrap_or(latest_path)
1750 .to_string_lossy()
1751 .replace('\\', "/");
1752 Some(relative)
1753 } else {
1754 None
1755 }
1756}
1757
1758#[cfg(test)]
1759#[path = "driver_tests.rs"]
1760mod tests;