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