1use anyhow::{Context, Result, anyhow, bail};
2use serde::{Deserialize, Deserializer};
3use std::collections::VecDeque;
4
5use rustc_hash::{FxHashMap, FxHashSet};
6use std::env;
7use std::path::{Path, PathBuf};
8
9use crate::checker::context::ScriptTarget as CheckerScriptTarget;
10use crate::checker::diagnostics::Diagnostic;
11use crate::emitter::{ModuleKind, PrinterOptions, ScriptTarget};
12use tsz_common::diagnostics::data::{diagnostic_codes, diagnostic_messages};
13use tsz_common::diagnostics::format_message;
14
15fn deserialize_bool_or_string<'de, D>(deserializer: D) -> Result<Option<bool>, D::Error>
18where
19 D: Deserializer<'de>,
20{
21 use serde::de::Error;
22
23 #[derive(Deserialize)]
25 #[serde(untagged)]
26 enum BoolOrString {
27 Bool(bool),
28 String(String),
29 }
30
31 match Option::<BoolOrString>::deserialize(deserializer)? {
32 None => Ok(None),
33 Some(BoolOrString::Bool(b)) => Ok(Some(b)),
34 Some(BoolOrString::String(s)) => {
35 let normalized = s.trim().to_lowercase();
37 match normalized.as_str() {
38 "true" | "1" | "yes" | "on" => Ok(Some(true)),
39 "false" | "0" | "no" | "off" => Ok(Some(false)),
40 _ => {
41 Err(Error::custom(format!(
43 "invalid boolean value: '{s}'. Expected true, false, 'true', or 'false'",
44 )))
45 }
46 }
47 }
48 }
49}
50
51#[derive(Debug, Clone, Deserialize, Default)]
52#[serde(rename_all = "camelCase")]
53pub struct TsConfig {
54 #[serde(default)]
55 pub extends: Option<String>,
56 #[serde(default)]
57 pub compiler_options: Option<CompilerOptions>,
58 #[serde(default)]
59 pub include: Option<Vec<String>>,
60 #[serde(default)]
61 pub exclude: Option<Vec<String>>,
62 #[serde(default)]
63 pub files: Option<Vec<String>>,
64}
65
66#[derive(Debug, Clone, Deserialize, Default)]
67#[serde(rename_all = "camelCase")]
68pub struct CompilerOptions {
69 #[serde(default)]
70 pub target: Option<String>,
71 #[serde(default)]
72 pub module: Option<String>,
73 #[serde(default)]
74 pub module_resolution: Option<String>,
75 #[serde(default, deserialize_with = "deserialize_bool_or_string")]
77 pub resolve_package_json_exports: Option<bool>,
78 #[serde(default, deserialize_with = "deserialize_bool_or_string")]
80 pub resolve_package_json_imports: Option<bool>,
81 #[serde(default)]
83 pub module_suffixes: Option<Vec<String>>,
84 #[serde(default, deserialize_with = "deserialize_bool_or_string")]
86 pub resolve_json_module: Option<bool>,
87 #[serde(default, deserialize_with = "deserialize_bool_or_string")]
89 pub allow_arbitrary_extensions: Option<bool>,
90 #[serde(default, deserialize_with = "deserialize_bool_or_string")]
92 pub allow_importing_ts_extensions: Option<bool>,
93 #[serde(default, deserialize_with = "deserialize_bool_or_string")]
95 pub rewrite_relative_import_extensions: Option<bool>,
96 #[serde(default)]
97 pub types_versions_compiler_version: Option<String>,
98 #[serde(default)]
99 pub types: Option<Vec<String>>,
100 #[serde(default)]
101 pub type_roots: Option<Vec<String>>,
102 #[serde(default)]
103 pub jsx: Option<String>,
104 #[serde(default)]
105 #[serde(rename = "jsxFactory")]
106 pub jsx_factory: Option<String>,
107 #[serde(default)]
108 #[serde(rename = "jsxFragmentFactory")]
109 pub jsx_fragment_factory: Option<String>,
110 #[serde(default)]
111 #[serde(rename = "reactNamespace")]
112 pub react_namespace: Option<String>,
113
114 #[serde(default)]
115 pub lib: Option<Vec<String>>,
116 #[serde(default, deserialize_with = "deserialize_bool_or_string")]
117 pub no_lib: Option<bool>,
118 #[serde(default, deserialize_with = "deserialize_bool_or_string")]
119 pub no_types_and_symbols: Option<bool>,
120 #[serde(default)]
121 pub base_url: Option<String>,
122 #[serde(default)]
123 pub paths: Option<FxHashMap<String, Vec<String>>>,
124 #[serde(default)]
125 pub root_dir: Option<String>,
126 #[serde(default)]
127 pub out_dir: Option<String>,
128 #[serde(default)]
129 pub out_file: Option<String>,
130 #[serde(default, deserialize_with = "deserialize_bool_or_string")]
131 pub declaration: Option<bool>,
132 #[serde(default)]
133 pub declaration_dir: Option<String>,
134 #[serde(default, deserialize_with = "deserialize_bool_or_string")]
135 pub source_map: Option<bool>,
136 #[serde(default, deserialize_with = "deserialize_bool_or_string")]
137 pub declaration_map: Option<bool>,
138 #[serde(default)]
139 pub ts_build_info_file: Option<String>,
140 #[serde(default, deserialize_with = "deserialize_bool_or_string")]
141 pub incremental: Option<bool>,
142 #[serde(default, deserialize_with = "deserialize_bool_or_string")]
143 pub strict: Option<bool>,
144 #[serde(default, deserialize_with = "deserialize_bool_or_string")]
145 pub no_emit: Option<bool>,
146 #[serde(default, deserialize_with = "deserialize_bool_or_string")]
147 pub no_resolve: Option<bool>,
148 #[serde(default, deserialize_with = "deserialize_bool_or_string")]
149 pub no_emit_on_error: Option<bool>,
150 #[serde(default, deserialize_with = "deserialize_bool_or_string")]
151 pub isolated_modules: Option<bool>,
152 #[serde(default)]
154 pub custom_conditions: Option<Vec<String>>,
155 #[serde(default, deserialize_with = "deserialize_bool_or_string")]
157 pub es_module_interop: Option<bool>,
158 #[serde(default, deserialize_with = "deserialize_bool_or_string")]
160 pub allow_synthetic_default_imports: Option<bool>,
161 #[serde(default, deserialize_with = "deserialize_bool_or_string")]
163 pub experimental_decorators: Option<bool>,
164 #[serde(default, deserialize_with = "deserialize_bool_or_string")]
166 pub import_helpers: Option<bool>,
167 #[serde(default, deserialize_with = "deserialize_bool_or_string")]
169 pub allow_js: Option<bool>,
170 #[serde(default, deserialize_with = "deserialize_bool_or_string")]
172 pub check_js: Option<bool>,
173 #[serde(default, deserialize_with = "deserialize_bool_or_string")]
175 pub skip_lib_check: Option<bool>,
176 #[serde(default, deserialize_with = "deserialize_bool_or_string")]
178 pub always_strict: Option<bool>,
179 #[serde(default, deserialize_with = "deserialize_bool_or_string")]
181 pub use_define_for_class_fields: Option<bool>,
182 #[serde(
184 default,
185 alias = "noImplicitAny",
186 deserialize_with = "deserialize_bool_or_string"
187 )]
188 pub no_implicit_any: Option<bool>,
189 #[serde(
191 default,
192 alias = "noImplicitReturns",
193 deserialize_with = "deserialize_bool_or_string"
194 )]
195 pub no_implicit_returns: Option<bool>,
196 #[serde(
198 default,
199 alias = "strictNullChecks",
200 deserialize_with = "deserialize_bool_or_string"
201 )]
202 pub strict_null_checks: Option<bool>,
203 #[serde(
205 default,
206 alias = "strictFunctionTypes",
207 deserialize_with = "deserialize_bool_or_string"
208 )]
209 pub strict_function_types: Option<bool>,
210 #[serde(
212 default,
213 alias = "strictPropertyInitialization",
214 deserialize_with = "deserialize_bool_or_string"
215 )]
216 pub strict_property_initialization: Option<bool>,
217 #[serde(
219 default,
220 alias = "noImplicitThis",
221 deserialize_with = "deserialize_bool_or_string"
222 )]
223 pub no_implicit_this: Option<bool>,
224 #[serde(
226 default,
227 alias = "useUnknownInCatchVariables",
228 deserialize_with = "deserialize_bool_or_string"
229 )]
230 pub use_unknown_in_catch_variables: Option<bool>,
231 #[serde(
233 default,
234 alias = "noUncheckedIndexedAccess",
235 deserialize_with = "deserialize_bool_or_string"
236 )]
237 pub no_unchecked_indexed_access: Option<bool>,
238 #[serde(
240 default,
241 alias = "strictBindCallApply",
242 deserialize_with = "deserialize_bool_or_string"
243 )]
244 pub strict_bind_call_apply: Option<bool>,
245 #[serde(default, deserialize_with = "deserialize_bool_or_string")]
247 pub no_unused_locals: Option<bool>,
248 #[serde(default, deserialize_with = "deserialize_bool_or_string")]
250 pub no_unused_parameters: Option<bool>,
251 #[serde(default, deserialize_with = "deserialize_bool_or_string")]
253 pub allow_unreachable_code: Option<bool>,
254 #[serde(default, deserialize_with = "deserialize_bool_or_string")]
256 pub no_unchecked_side_effect_imports: Option<bool>,
257 #[serde(
259 default,
260 alias = "noImplicitOverride",
261 deserialize_with = "deserialize_bool_or_string"
262 )]
263 pub no_implicit_override: Option<bool>,
264 #[serde(default)]
266 pub module_detection: Option<String>,
267}
268
269pub use crate::checker::context::CheckerOptions;
271
272#[derive(Debug, Clone, Default)]
273pub struct ResolvedCompilerOptions {
274 pub printer: PrinterOptions,
275 pub checker: CheckerOptions,
276 pub jsx: Option<JsxEmit>,
277 pub lib_files: Vec<PathBuf>,
278 pub lib_is_default: bool,
279 pub module_resolution: Option<ModuleResolutionKind>,
280 pub resolve_package_json_exports: bool,
281 pub resolve_package_json_imports: bool,
282 pub module_suffixes: Vec<String>,
283 pub resolve_json_module: bool,
284 pub allow_arbitrary_extensions: bool,
285 pub allow_importing_ts_extensions: bool,
286 pub rewrite_relative_import_extensions: bool,
287 pub types_versions_compiler_version: Option<String>,
288 pub types: Option<Vec<String>>,
289 pub type_roots: Option<Vec<PathBuf>>,
290 pub base_url: Option<PathBuf>,
291 pub paths: Option<Vec<PathMapping>>,
292 pub root_dir: Option<PathBuf>,
293 pub out_dir: Option<PathBuf>,
294 pub out_file: Option<PathBuf>,
295 pub declaration_dir: Option<PathBuf>,
296 pub emit_declarations: bool,
297 pub source_map: bool,
298 pub declaration_map: bool,
299 pub ts_build_info_file: Option<PathBuf>,
300 pub incremental: bool,
301 pub no_emit: bool,
302 pub no_emit_on_error: bool,
303 pub no_resolve: bool,
305 pub import_helpers: bool,
306 pub no_check: bool,
308 pub custom_conditions: Vec<String>,
310 pub es_module_interop: bool,
312 pub allow_synthetic_default_imports: bool,
314 pub allow_js: bool,
316 pub check_js: bool,
318 pub skip_lib_check: bool,
320}
321
322#[derive(Debug, Clone, Copy, PartialEq, Eq)]
323pub enum JsxEmit {
324 Preserve,
325 React,
326 ReactJsx,
327 ReactJsxDev,
328 ReactNative,
329}
330
331#[derive(Debug, Clone, Copy, PartialEq, Eq)]
332pub enum ModuleResolutionKind {
333 Classic,
334 Node,
335 Node16,
336 NodeNext,
337 Bundler,
338}
339
340#[derive(Debug, Clone)]
341pub struct PathMapping {
342 pub pattern: String,
343 pub(crate) prefix: String,
344 pub(crate) suffix: String,
345 pub targets: Vec<String>,
346}
347
348impl PathMapping {
349 pub fn match_specifier(&self, specifier: &str) -> Option<String> {
350 if !self.pattern.contains('*') {
351 return (self.pattern == specifier).then(String::new);
352 }
353
354 if !specifier.starts_with(&self.prefix) || !specifier.ends_with(&self.suffix) {
355 return None;
356 }
357
358 let start = self.prefix.len();
359 let end = specifier.len().saturating_sub(self.suffix.len());
360 if end < start {
361 return None;
362 }
363
364 Some(specifier[start..end].to_string())
365 }
366
367 pub const fn specificity(&self) -> usize {
368 self.prefix.len() + self.suffix.len()
369 }
370}
371
372impl ResolvedCompilerOptions {
373 pub const fn effective_module_resolution(&self) -> ModuleResolutionKind {
374 if let Some(resolution) = self.module_resolution {
375 return resolution;
376 }
377
378 match self.printer.module {
385 ModuleKind::None | ModuleKind::AMD | ModuleKind::UMD | ModuleKind::System => {
386 ModuleResolutionKind::Classic
387 }
388 ModuleKind::CommonJS => ModuleResolutionKind::Node,
389 ModuleKind::NodeNext => ModuleResolutionKind::NodeNext,
390 ModuleKind::Node16 => ModuleResolutionKind::Node16,
391 _ => ModuleResolutionKind::Bundler,
392 }
393 }
394}
395
396pub fn resolve_compiler_options(
397 options: Option<&CompilerOptions>,
398) -> Result<ResolvedCompilerOptions> {
399 let mut resolved = ResolvedCompilerOptions::default();
400 let Some(options) = options else {
401 resolved.checker.target = checker_target_from_emitter(resolved.printer.target);
402 resolved.lib_files = resolve_default_lib_files(resolved.printer.target)?;
403 resolved.lib_is_default = true;
404 resolved.module_suffixes = vec![String::new()];
405 let default_resolution = resolved.effective_module_resolution();
406 resolved.resolve_package_json_exports = matches!(
407 default_resolution,
408 ModuleResolutionKind::Node16
409 | ModuleResolutionKind::NodeNext
410 | ModuleResolutionKind::Bundler
411 );
412 resolved.resolve_package_json_imports = resolved.resolve_package_json_exports;
413 return Ok(resolved);
414 };
415
416 if let Some(target) = options.target.as_deref() {
417 resolved.printer.target = parse_script_target(target)?;
418 }
419 resolved.checker.target = checker_target_from_emitter(resolved.printer.target);
420
421 let module_explicitly_set = options.module.is_some();
422 if let Some(module) = options.module.as_deref() {
423 let kind = parse_module_kind(module)?;
424 resolved.printer.module = kind;
425 resolved.checker.module = kind;
426 } else {
427 let default_module = if resolved.printer.target.supports_es2015() {
430 ModuleKind::ES2015
431 } else {
432 ModuleKind::CommonJS
433 };
434 resolved.printer.module = default_module;
435 resolved.checker.module = default_module;
436 }
437 resolved.checker.module_explicitly_set = module_explicitly_set;
438
439 if let Some(module_resolution) = options.module_resolution.as_deref() {
440 let value = module_resolution.trim();
441 if !value.is_empty() {
442 resolved.module_resolution = Some(parse_module_resolution(value)?);
443 }
444 }
445
446 if !module_explicitly_set && let Some(mr) = resolved.module_resolution {
449 let inferred = match mr {
450 ModuleResolutionKind::Node16 => Some(ModuleKind::Node16),
451 ModuleResolutionKind::NodeNext => Some(ModuleKind::NodeNext),
452 _ => None,
453 };
454 if let Some(kind) = inferred {
455 resolved.printer.module = kind;
456 resolved.checker.module = kind;
457 }
458 }
459 let effective_resolution = resolved.effective_module_resolution();
460 resolved.resolve_package_json_exports = options.resolve_package_json_exports.unwrap_or({
461 matches!(
462 effective_resolution,
463 ModuleResolutionKind::Node16
464 | ModuleResolutionKind::NodeNext
465 | ModuleResolutionKind::Bundler
466 )
467 });
468 resolved.resolve_package_json_imports = options.resolve_package_json_imports.unwrap_or({
469 matches!(
470 effective_resolution,
471 ModuleResolutionKind::Node
472 | ModuleResolutionKind::Node16
473 | ModuleResolutionKind::NodeNext
474 | ModuleResolutionKind::Bundler
475 )
476 });
477 if let Some(module_suffixes) = options.module_suffixes.as_ref() {
478 resolved.module_suffixes = module_suffixes.clone();
479 } else {
480 resolved.module_suffixes = vec![String::new()];
481 }
482 if let Some(resolve_json_module) = options.resolve_json_module {
483 resolved.resolve_json_module = resolve_json_module;
484 resolved.checker.resolve_json_module = resolve_json_module;
485 }
486 if let Some(import_helpers) = options.import_helpers {
487 resolved.import_helpers = import_helpers;
488 }
489 if let Some(allow_arbitrary_extensions) = options.allow_arbitrary_extensions {
490 resolved.allow_arbitrary_extensions = allow_arbitrary_extensions;
491 }
492 if let Some(allow_importing_ts_extensions) = options.allow_importing_ts_extensions {
493 resolved.allow_importing_ts_extensions = allow_importing_ts_extensions;
494 }
495 if let Some(rewrite_relative_import_extensions) = options.rewrite_relative_import_extensions {
496 resolved.rewrite_relative_import_extensions = rewrite_relative_import_extensions;
497 }
498
499 if let Some(types_versions_compiler_version) =
500 options.types_versions_compiler_version.as_deref()
501 {
502 let value = types_versions_compiler_version.trim();
503 if !value.is_empty() {
504 resolved.types_versions_compiler_version = Some(value.to_string());
505 }
506 }
507
508 if let Some(types) = options.types.as_ref() {
509 let list: Vec<String> = types
510 .iter()
511 .filter_map(|value| {
512 let trimmed = value.trim();
513 if trimmed.is_empty() {
514 None
515 } else {
516 Some(trimmed.to_string())
517 }
518 })
519 .collect();
520 resolved.types = Some(list);
521 }
522
523 if let Some(type_roots) = options.type_roots.as_ref() {
524 let roots: Vec<PathBuf> = type_roots
525 .iter()
526 .filter_map(|value| {
527 let trimmed = value.trim();
528 if trimmed.is_empty() {
529 None
530 } else {
531 Some(PathBuf::from(trimmed))
532 }
533 })
534 .collect();
535 resolved.type_roots = Some(roots);
536 }
537
538 if let Some(factory) = options.jsx_factory.as_deref() {
539 resolved.checker.jsx_factory = factory.to_string();
540 } else if let Some(ns) = options.react_namespace.as_deref() {
541 resolved.checker.jsx_factory = format!("{ns}.createElement");
542 }
543 if let Some(frag) = options.jsx_fragment_factory.as_deref() {
544 resolved.checker.jsx_fragment_factory = frag.to_string();
545 }
546
547 if let Some(jsx) = options.jsx.as_deref() {
548 let jsx_emit = parse_jsx_emit(jsx)?;
549 resolved.jsx = Some(jsx_emit);
550 resolved.checker.jsx_mode = jsx_emit_to_mode(jsx_emit);
551 }
552
553 if let Some(no_lib) = options.no_lib {
554 resolved.checker.no_lib = no_lib;
555 }
556
557 if resolved.checker.no_lib && options.lib.is_some() {
558 return Err(anyhow::anyhow!(
559 "Option 'lib' cannot be specified with option 'noLib'."
560 ));
561 }
562
563 if let Some(no_types_and_symbols) = options.no_types_and_symbols {
564 resolved.checker.no_types_and_symbols = no_types_and_symbols;
565 }
566
567 if resolved.checker.no_lib && options.lib.is_some() {
568 bail!("Option 'lib' cannot be specified with option 'noLib'.");
569 }
570
571 if let Some(lib_list) = options.lib.as_ref() {
572 resolved.lib_files = resolve_lib_files(lib_list)?;
573 resolved.lib_is_default = false;
574 } else if !resolved.checker.no_lib && !resolved.checker.no_types_and_symbols {
575 resolved.lib_files = resolve_default_lib_files(resolved.printer.target)?;
576 resolved.lib_is_default = true;
577 }
578
579 let base_url = options.base_url.as_deref().map(str::trim);
580 if let Some(base_url) = base_url
581 && !base_url.is_empty()
582 {
583 resolved.base_url = Some(PathBuf::from(base_url));
584 }
585
586 if let Some(paths) = options.paths.as_ref()
587 && !paths.is_empty()
588 {
589 resolved.paths = Some(build_path_mappings(paths));
590 }
591
592 if let Some(root_dir) = options.root_dir.as_deref()
593 && !root_dir.is_empty()
594 {
595 resolved.root_dir = Some(PathBuf::from(root_dir));
596 }
597
598 if let Some(out_dir) = options.out_dir.as_deref()
599 && !out_dir.is_empty()
600 {
601 resolved.out_dir = Some(PathBuf::from(out_dir));
602 }
603
604 if let Some(out_file) = options.out_file.as_deref()
605 && !out_file.is_empty()
606 {
607 resolved.out_file = Some(PathBuf::from(out_file));
608 }
609
610 if let Some(declaration_dir) = options.declaration_dir.as_deref()
611 && !declaration_dir.is_empty()
612 {
613 resolved.declaration_dir = Some(PathBuf::from(declaration_dir));
614 }
615
616 if let Some(declaration) = options.declaration {
617 resolved.emit_declarations = declaration;
618 }
619
620 if let Some(source_map) = options.source_map {
621 resolved.source_map = source_map;
622 }
623
624 if let Some(declaration_map) = options.declaration_map {
625 resolved.declaration_map = declaration_map;
626 }
627
628 if let Some(ts_build_info_file) = options.ts_build_info_file.as_deref()
629 && !ts_build_info_file.is_empty()
630 {
631 resolved.ts_build_info_file = Some(PathBuf::from(ts_build_info_file));
632 }
633
634 if let Some(incremental) = options.incremental {
635 resolved.incremental = incremental;
636 }
637
638 if let Some(strict) = options.strict {
639 resolved.checker.strict = strict;
640 if strict {
641 resolved.checker.no_implicit_any = true;
642 resolved.checker.strict_null_checks = true;
643 resolved.checker.strict_function_types = true;
644 resolved.checker.strict_bind_call_apply = true;
645 resolved.checker.strict_property_initialization = true;
646 resolved.checker.no_implicit_this = true;
647 resolved.checker.use_unknown_in_catch_variables = true;
648 resolved.checker.always_strict = true;
649 resolved.printer.always_strict = true;
650 } else {
651 resolved.checker.no_implicit_any = false;
652 resolved.checker.strict_null_checks = false;
653 resolved.checker.strict_function_types = false;
654 resolved.checker.strict_bind_call_apply = false;
655 resolved.checker.strict_property_initialization = false;
656 resolved.checker.no_implicit_this = false;
657 resolved.checker.use_unknown_in_catch_variables = false;
658 resolved.checker.always_strict = false;
659 resolved.printer.always_strict = false;
660 }
661 }
662
663 if options.strict.is_none() {
667 resolved.checker.strict = false;
668 resolved.checker.no_implicit_any = false;
669 resolved.checker.strict_null_checks = false;
670 resolved.checker.strict_function_types = false;
671 resolved.checker.strict_bind_call_apply = false;
672 resolved.checker.strict_property_initialization = false;
673 resolved.checker.no_implicit_this = false;
674 resolved.checker.use_unknown_in_catch_variables = false;
675 resolved.checker.always_strict = false;
676 resolved.printer.always_strict = false;
677 }
678
679 if let Some(v) = options.no_implicit_any {
681 resolved.checker.no_implicit_any = v;
682 }
683 if let Some(v) = options.no_implicit_returns {
684 resolved.checker.no_implicit_returns = v;
685 }
686 if let Some(v) = options.strict_null_checks {
687 resolved.checker.strict_null_checks = v;
688 }
689 if let Some(v) = options.strict_function_types {
690 resolved.checker.strict_function_types = v;
691 }
692 if let Some(v) = options.strict_property_initialization {
693 resolved.checker.strict_property_initialization = v;
694 }
695 if let Some(v) = options.no_unchecked_indexed_access {
696 resolved.checker.no_unchecked_indexed_access = v;
697 }
698 if let Some(v) = options.no_implicit_this {
699 resolved.checker.no_implicit_this = v;
700 }
701 if let Some(v) = options.use_unknown_in_catch_variables {
702 resolved.checker.use_unknown_in_catch_variables = v;
703 }
704 if let Some(v) = options.strict_bind_call_apply {
705 resolved.checker.strict_bind_call_apply = v;
706 }
707 if let Some(v) = options.no_implicit_override {
708 resolved.checker.no_implicit_override = v;
709 }
710 if let Some(v) = options.no_unchecked_side_effect_imports {
711 resolved.checker.no_unchecked_side_effect_imports = v;
712 }
713
714 if let Some(no_emit) = options.no_emit {
715 resolved.no_emit = no_emit;
716 }
717 if let Some(no_resolve) = options.no_resolve {
718 resolved.no_resolve = no_resolve;
719 resolved.checker.no_resolve = no_resolve;
720 }
721
722 if let Some(no_emit_on_error) = options.no_emit_on_error {
723 resolved.no_emit_on_error = no_emit_on_error;
724 }
725
726 if let Some(isolated_modules) = options.isolated_modules {
727 resolved.checker.isolated_modules = isolated_modules;
728 }
729
730 if let Some(always_strict) = options.always_strict {
731 resolved.checker.always_strict = always_strict;
732 resolved.printer.always_strict = always_strict;
733 }
734
735 if let Some(use_define_for_class_fields) = options.use_define_for_class_fields {
736 resolved.printer.use_define_for_class_fields = use_define_for_class_fields;
737 }
738
739 if let Some(no_unused_locals) = options.no_unused_locals {
740 resolved.checker.no_unused_locals = no_unused_locals;
741 }
742
743 if let Some(no_unused_parameters) = options.no_unused_parameters {
744 resolved.checker.no_unused_parameters = no_unused_parameters;
745 }
746
747 if let Some(allow_unreachable_code) = options.allow_unreachable_code {
748 resolved.checker.allow_unreachable_code = Some(allow_unreachable_code);
749 }
750
751 if let Some(ref custom_conditions) = options.custom_conditions {
752 resolved.custom_conditions = custom_conditions.clone();
753 }
754
755 if let Some(es_module_interop) = options.es_module_interop {
756 resolved.es_module_interop = es_module_interop;
757 resolved.checker.es_module_interop = es_module_interop;
758 resolved.printer.es_module_interop = es_module_interop;
759 if es_module_interop {
761 resolved.allow_synthetic_default_imports = true;
762 resolved.checker.allow_synthetic_default_imports = true;
763 }
764 }
765
766 if let Some(allow_synthetic_default_imports) = options.allow_synthetic_default_imports {
767 resolved.allow_synthetic_default_imports = allow_synthetic_default_imports;
768 resolved.checker.allow_synthetic_default_imports = allow_synthetic_default_imports;
769 } else if !resolved.allow_synthetic_default_imports {
770 let should_default_true = matches!(resolved.checker.module, ModuleKind::System)
776 || matches!(
777 resolved.module_resolution,
778 Some(ModuleResolutionKind::Bundler)
779 );
780 if should_default_true {
781 resolved.allow_synthetic_default_imports = true;
782 resolved.checker.allow_synthetic_default_imports = true;
783 }
784 }
785
786 if let Some(experimental_decorators) = options.experimental_decorators {
787 resolved.checker.experimental_decorators = experimental_decorators;
788 resolved.printer.legacy_decorators = experimental_decorators;
789 }
790
791 if let Some(allow_js) = options.allow_js {
792 resolved.allow_js = allow_js;
793 }
794
795 if let Some(check_js) = options.check_js {
796 resolved.check_js = check_js;
797 }
798 if let Some(skip_lib_check) = options.skip_lib_check {
799 resolved.skip_lib_check = skip_lib_check;
800 }
801
802 if let Some(ref module_detection) = options.module_detection
803 && module_detection.eq_ignore_ascii_case("force")
804 {
805 resolved.printer.module_detection_force = true;
806 }
807
808 Ok(resolved)
809}
810
811pub fn parse_tsconfig(source: &str) -> Result<TsConfig> {
812 let stripped = strip_jsonc(source);
813 let normalized = remove_trailing_commas(&stripped);
814 let config = serde_json::from_str(&normalized).context("failed to parse tsconfig JSON")?;
815 Ok(config)
816}
817
818pub struct ParsedTsConfig {
820 pub config: TsConfig,
821 pub diagnostics: Vec<Diagnostic>,
822 pub suppress_excess_property_errors: bool,
825 pub suppress_implicit_any_index_errors: bool,
828}
829
830pub fn parse_tsconfig_with_diagnostics(source: &str, file_path: &str) -> Result<ParsedTsConfig> {
837 let stripped = strip_jsonc(source);
838 let normalized = remove_trailing_commas(&stripped);
839 let mut raw: serde_json::Value =
840 serde_json::from_str(&normalized).context("failed to parse tsconfig JSON")?;
841
842 let mut diagnostics = Vec::new();
843 let mut suppress_excess = false;
844 let mut suppress_any_index = false;
845
846 if let Some(obj) = raw.as_object_mut()
848 && let Some(serde_json::Value::Object(compiler_opts)) = obj.get_mut("compilerOptions")
849 {
850 let keys: Vec<String> = compiler_opts.keys().cloned().collect();
851 let mut renames: Vec<(String, String)> = Vec::new();
852
853 for key in &keys {
854 let key_lower = key.to_lowercase();
855 if let Some(canonical) = known_compiler_option(&key_lower) {
856 if key.as_str() != canonical {
857 let start = find_key_offset_in_source(&stripped, key);
859 let msg = format_message(
860 diagnostic_messages::UNKNOWN_COMPILER_OPTION_DID_YOU_MEAN,
861 &[key, canonical],
862 );
863 diagnostics.push(Diagnostic::error(
864 file_path,
865 start,
866 key.len() as u32 + 2, msg,
868 diagnostic_codes::UNKNOWN_COMPILER_OPTION_DID_YOU_MEAN,
869 ));
870 renames.push((key.clone(), canonical.to_string()));
871 }
872 } else {
874 let start = find_key_offset_in_source(&stripped, key);
876 let msg = format_message(diagnostic_messages::UNKNOWN_COMPILER_OPTION, &[key]);
877 diagnostics.push(Diagnostic::error(
878 file_path,
879 start,
880 key.len() as u32 + 2,
881 msg,
882 diagnostic_codes::UNKNOWN_COMPILER_OPTION,
883 ));
884 }
885 }
886
887 for (old_key, new_key) in renames {
889 if let Some(value) = compiler_opts.remove(&old_key) {
890 compiler_opts.insert(new_key, value);
891 }
892 }
893
894 let ignore_deprecations_valid = matches!(
898 compiler_opts.get("ignoreDeprecations"),
899 Some(serde_json::Value::String(v)) if v == "5.0"
900 );
901 let mut removed_keys: Vec<String> = Vec::new();
902 for key in compiler_opts.keys().cloned().collect::<Vec<_>>() {
903 if removed_compiler_option(&key).is_some() {
904 if !ignore_deprecations_valid {
905 let value = compiler_opts.get(&key);
906 let is_set = match value {
908 Some(serde_json::Value::Bool(b)) => *b,
909 Some(serde_json::Value::String(s)) => !s.is_empty(),
910 Some(serde_json::Value::Null) | None => false,
911 Some(_) => true,
912 };
913 if is_set {
914 let start = find_key_offset_in_source(&stripped, &key);
915 let msg = format_message(
916 diagnostic_messages::OPTION_HAS_BEEN_REMOVED_PLEASE_REMOVE_IT_FROM_YOUR_CONFIGURATION,
917 &[&key],
918 );
919 diagnostics.push(Diagnostic::error(
920 file_path,
921 start,
922 key.len() as u32 + 2, msg,
924 diagnostic_codes::OPTION_HAS_BEEN_REMOVED_PLEASE_REMOVE_IT_FROM_YOUR_CONFIGURATION,
925 ));
926 }
927 }
928 removed_keys.push(key);
929 }
930 }
931 suppress_excess = matches!(
934 compiler_opts.get("suppressExcessPropertyErrors"),
935 Some(serde_json::Value::Bool(true))
936 );
937 suppress_any_index = matches!(
938 compiler_opts.get("suppressImplicitAnyIndexErrors"),
939 Some(serde_json::Value::Bool(true))
940 );
941
942 for key in &removed_keys {
944 compiler_opts.remove(key);
945 }
946
947 let keys_after_rename: Vec<String> = compiler_opts.keys().cloned().collect();
950 let mut bad_keys: Vec<String> = Vec::new();
951 for key in &keys_after_rename {
952 let expected_type = compiler_option_expected_type(key);
953 if expected_type.is_empty() {
954 continue; }
956 let Some(value) = compiler_opts.get(key) else {
957 continue;
958 };
959 let type_ok = match expected_type {
960 "boolean" => value.is_boolean(),
961 "string" => value.is_string(),
962 "number" => value.is_number(),
963 "list" => value.is_array(),
964 "string or Array" => value.is_string() || value.is_array(),
965 "object" => value.is_object(),
966 _ => true,
967 };
968 if !type_ok {
969 let start = find_value_offset_in_source(&stripped, key);
970 let value_len = estimate_json_value_len(value);
971 let msg = format_message(
972 diagnostic_messages::COMPILER_OPTION_REQUIRES_A_VALUE_OF_TYPE,
973 &[key, expected_type],
974 );
975 diagnostics.push(Diagnostic::error(
976 file_path,
977 start,
978 value_len,
979 msg,
980 diagnostic_codes::COMPILER_OPTION_REQUIRES_A_VALUE_OF_TYPE,
981 ));
982 bad_keys.push(key.clone());
983 }
984 }
985 for key in &bad_keys {
987 compiler_opts.remove(key);
988 }
989
990 if let Some(serde_json::Value::String(id_value)) = compiler_opts.get("ignoreDeprecations")
993 && id_value != "5.0"
994 {
995 let start = find_value_offset_in_source(&stripped, "ignoreDeprecations");
996 let value_len = id_value.len() as u32 + 2; diagnostics.push(Diagnostic::error(
998 file_path,
999 start,
1000 value_len,
1001 diagnostic_messages::INVALID_VALUE_FOR_IGNOREDEPRECATIONS.to_string(),
1002 diagnostic_codes::INVALID_VALUE_FOR_IGNOREDEPRECATIONS,
1003 ));
1004 }
1005
1006 if let Some(serde_json::Value::String(mr_value)) = compiler_opts.get("moduleResolution") {
1009 let mr_normalized =
1010 normalize_option(mr_value.split(',').next().unwrap_or(mr_value).trim());
1011 if mr_normalized == "bundler" {
1012 let module_ok = if let Some(serde_json::Value::String(mod_value)) =
1013 compiler_opts.get("module")
1014 {
1015 let mod_normalized =
1016 normalize_option(mod_value.split(',').next().unwrap_or(mod_value).trim());
1017 matches!(
1018 mod_normalized.as_str(),
1019 "preserve"
1020 | "es2015"
1021 | "es6"
1022 | "es2020"
1023 | "es2022"
1024 | "esnext"
1025 | "node16"
1026 | "node18"
1027 | "node20"
1028 | "nodenext"
1029 )
1030 } else {
1031 if let Some(serde_json::Value::String(target_value)) =
1035 compiler_opts.get("target")
1036 {
1037 let target_normalized = normalize_option(
1038 target_value
1039 .split(',')
1040 .next()
1041 .unwrap_or(target_value)
1042 .trim(),
1043 );
1044 matches!(
1045 target_normalized.as_str(),
1046 "es2015"
1047 | "es6"
1048 | "es2016"
1049 | "es2017"
1050 | "es2018"
1051 | "es2019"
1052 | "es2020"
1053 | "es2021"
1054 | "es2022"
1055 | "es2023"
1056 | "es2024"
1057 | "esnext"
1058 )
1059 } else {
1060 false
1062 }
1063 };
1064 if !module_ok {
1065 let start = find_value_offset_in_source(&stripped, "moduleResolution");
1066 let value_len = mr_value.len() as u32 + 2; let msg = "Option 'bundler' can only be used when 'module' is set to 'preserve' or to 'es2015' or later.".to_string();
1070 diagnostics.push(Diagnostic::error(
1071 file_path,
1072 start,
1073 value_len,
1074 msg,
1075 diagnostic_codes::OPTION_CAN_ONLY_BE_USED_WHEN_MODULE_IS_SET_TO_PRESERVE_COMMONJS_OR_ES2015_OR_LAT,
1076 ));
1077 }
1078 }
1079 }
1080
1081 if let Some(serde_json::Value::String(mr_value)) = compiler_opts.get("moduleResolution") {
1084 let mr_normalized =
1085 normalize_option(mr_value.split(',').next().unwrap_or(mr_value).trim());
1086 let is_node_mr = matches!(
1087 mr_normalized.as_str(),
1088 "node16" | "node18" | "node20" | "nodenext"
1089 );
1090 if is_node_mr {
1091 let module_ok = if let Some(serde_json::Value::String(mod_value)) =
1092 compiler_opts.get("module")
1093 {
1094 let mod_normalized =
1095 normalize_option(mod_value.split(',').next().unwrap_or(mod_value).trim());
1096 matches!(
1097 mod_normalized.as_str(),
1098 "node16" | "node18" | "node20" | "nodenext"
1099 )
1100 } else {
1101 true };
1103 if !module_ok {
1104 let start = find_value_offset_in_source(&stripped, "module");
1105 let value_len = compiler_opts
1106 .get("module")
1107 .and_then(|v| v.as_str())
1108 .map_or(0, |s| s.len() as u32 + 2);
1109 let mr_display = match mr_normalized.as_str() {
1111 "node16" => "Node16",
1112 "node18" => "Node18",
1113 "node20" => "Node20",
1114 "nodenext" => "NodeNext",
1115 _ => &mr_normalized,
1116 };
1117 let msg = format_message(
1118 diagnostic_messages::OPTION_MODULE_MUST_BE_SET_TO_WHEN_OPTION_MODULERESOLUTION_IS_SET_TO,
1119 &[mr_display, mr_display],
1120 );
1121 diagnostics.push(Diagnostic::error(
1122 file_path,
1123 start,
1124 value_len,
1125 msg,
1126 diagnostic_codes::OPTION_MODULE_MUST_BE_SET_TO_WHEN_OPTION_MODULERESOLUTION_IS_SET_TO,
1127 ));
1128 }
1129 }
1130 }
1131
1132 if let Some(serde_json::Value::String(out_file_value)) = compiler_opts.get("outFile")
1135 && !out_file_value.is_empty()
1136 && !option_is_truthy(compiler_opts.get("emitDeclarationOnly"))
1137 && let Some(serde_json::Value::String(mod_value)) = compiler_opts.get("module")
1138 {
1139 let mod_normalized =
1140 normalize_option(mod_value.split(',').next().unwrap_or(mod_value).trim());
1141 if !matches!(mod_normalized.as_str(), "amd" | "system") {
1142 let msg = format_message(
1143 diagnostic_messages::ONLY_AMD_AND_SYSTEM_MODULES_ARE_SUPPORTED_ALONGSIDE,
1144 &["outFile"],
1145 );
1146 let start_module = find_key_offset_in_source(&stripped, "module");
1148 let module_key_len = "module".len() as u32 + 2; diagnostics.push(Diagnostic::error(
1150 file_path,
1151 start_module,
1152 module_key_len,
1153 msg.clone(),
1154 diagnostic_codes::ONLY_AMD_AND_SYSTEM_MODULES_ARE_SUPPORTED_ALONGSIDE,
1155 ));
1156 let start_outfile = find_key_offset_in_source(&stripped, "outFile");
1158 let outfile_key_len = "outFile".len() as u32 + 2;
1159 diagnostics.push(Diagnostic::error(
1160 file_path,
1161 start_outfile,
1162 outfile_key_len,
1163 msg,
1164 diagnostic_codes::ONLY_AMD_AND_SYSTEM_MODULES_ARE_SUPPORTED_ALONGSIDE,
1165 ));
1166 }
1167 }
1168
1169 let requires_decl_or_composite: &[&str] = &[
1171 "emitDeclarationOnly",
1172 "declarationMap",
1173 "isolatedDeclarations",
1174 ];
1175 for &opt in requires_decl_or_composite {
1176 if option_is_truthy(compiler_opts.get(opt))
1177 && !option_is_truthy(compiler_opts.get("declaration"))
1178 && !option_is_truthy(compiler_opts.get("composite"))
1179 {
1180 let start = find_key_offset_in_source(&stripped, opt);
1181 let key_len = opt.len() as u32 + 2; let msg = format_message(
1183 diagnostic_messages::OPTION_CANNOT_BE_SPECIFIED_WITHOUT_SPECIFYING_OPTION_OR_OPTION,
1184 &[opt, "declaration", "composite"],
1185 );
1186 diagnostics.push(Diagnostic::error(
1187 file_path,
1188 start,
1189 key_len,
1190 msg,
1191 diagnostic_codes::OPTION_CANNOT_BE_SPECIFIED_WITHOUT_SPECIFYING_OPTION_OR_OPTION,
1192 ));
1193 }
1194 }
1195
1196 let conflicting_pairs: &[(&str, &str)] = &[
1200 ("sourceMap", "inlineSourceMap"),
1201 ("mapRoot", "inlineSourceMap"),
1202 ("reactNamespace", "jsxFactory"),
1203 ("allowJs", "isolatedDeclarations"),
1204 ];
1205 for &(opt_a, opt_b) in conflicting_pairs {
1206 if option_is_truthy(compiler_opts.get(opt_a))
1207 && option_is_truthy(compiler_opts.get(opt_b))
1208 {
1209 let start = find_key_offset_in_source(&stripped, opt_a);
1211 let key_len = opt_a.len() as u32 + 2;
1212 let msg = format_message(
1213 diagnostic_messages::OPTION_CANNOT_BE_SPECIFIED_WITH_OPTION,
1214 &[opt_a, opt_b],
1215 );
1216 diagnostics.push(Diagnostic::error(
1217 file_path,
1218 start,
1219 key_len,
1220 msg.clone(),
1221 diagnostic_codes::OPTION_CANNOT_BE_SPECIFIED_WITH_OPTION,
1222 ));
1223 let start_b = find_key_offset_in_source(&stripped, opt_b);
1225 let key_len_b = opt_b.len() as u32 + 2;
1226 diagnostics.push(Diagnostic::error(
1227 file_path,
1228 start_b,
1229 key_len_b,
1230 msg,
1231 diagnostic_codes::OPTION_CANNOT_BE_SPECIFIED_WITH_OPTION,
1232 ));
1233 }
1234 }
1235
1236 if option_is_truthy(compiler_opts.get("resolveJsonModule")) {
1239 let effective_mr = if let Some(serde_json::Value::String(mr_value)) =
1241 compiler_opts.get("moduleResolution")
1242 {
1243 normalize_option(mr_value.split(',').next().unwrap_or(mr_value).trim())
1244 } else {
1245 let effective_module = if let Some(serde_json::Value::String(mod_value)) =
1247 compiler_opts.get("module")
1248 {
1249 normalize_option(mod_value.split(',').next().unwrap_or(mod_value).trim())
1250 } else {
1251 String::new() };
1253 match effective_module.as_str() {
1254 "none" | "amd" | "umd" | "system" | "" => "classic".to_string(),
1255 "commonjs" => "node".to_string(),
1256 "node16" => "node16".to_string(),
1257 "nodenext" => "nodenext".to_string(),
1258 _ => "bundler".to_string(),
1259 }
1260 };
1261
1262 if effective_mr == "classic" {
1263 let start = find_key_offset_in_source(&stripped, "resolveJsonModule");
1264 let key_len = "resolveJsonModule".len() as u32 + 2;
1265 diagnostics.push(Diagnostic::error(
1266 file_path,
1267 start,
1268 key_len,
1269 diagnostic_messages::OPTION_RESOLVEJSONMODULE_CANNOT_BE_SPECIFIED_WHEN_MODULERESOLUTION_IS_SET_TO_CLA.to_string(),
1270 diagnostic_codes::OPTION_RESOLVEJSONMODULE_CANNOT_BE_SPECIFIED_WHEN_MODULERESOLUTION_IS_SET_TO_CLA,
1271 ));
1272 }
1273
1274 if let Some(serde_json::Value::String(mod_value)) = compiler_opts.get("module") {
1276 let mod_normalized =
1277 normalize_option(mod_value.split(',').next().unwrap_or(mod_value).trim());
1278 if matches!(mod_normalized.as_str(), "none" | "system" | "umd") {
1279 let start = find_key_offset_in_source(&stripped, "resolveJsonModule");
1280 let key_len = "resolveJsonModule".len() as u32 + 2;
1281 diagnostics.push(Diagnostic::error(
1282 file_path,
1283 start,
1284 key_len,
1285 diagnostic_messages::OPTION_RESOLVEJSONMODULE_CANNOT_BE_SPECIFIED_WHEN_MODULE_IS_SET_TO_NONE_SYSTEM_O.to_string(),
1286 diagnostic_codes::OPTION_RESOLVEJSONMODULE_CANNOT_BE_SPECIFIED_WHEN_MODULE_IS_SET_TO_NONE_SYSTEM_O,
1287 ));
1288 }
1289 }
1290 }
1291
1292 let requires_modern_mr: &[&str] = &[
1294 "resolvePackageJsonExports",
1295 "resolvePackageJsonImports",
1296 "customConditions",
1297 ];
1298 let mr_is_modern = if let Some(serde_json::Value::String(mr_value)) =
1299 compiler_opts.get("moduleResolution")
1300 {
1301 let mr_normalized =
1302 normalize_option(mr_value.split(',').next().unwrap_or(mr_value).trim());
1303 matches!(mr_normalized.as_str(), "node16" | "nodenext" | "bundler")
1304 } else {
1305 if let Some(serde_json::Value::String(mod_value)) = compiler_opts.get("module") {
1309 let mod_normalized =
1310 normalize_option(mod_value.split(',').next().unwrap_or(mod_value).trim());
1311 !matches!(
1312 mod_normalized.as_str(),
1313 "none" | "amd" | "umd" | "system" | "commonjs"
1314 )
1315 } else {
1316 false }
1318 };
1319 if !mr_is_modern {
1320 for &opt in requires_modern_mr {
1321 if option_is_truthy(compiler_opts.get(opt)) {
1322 let start = find_key_offset_in_source(&stripped, opt);
1323 let key_len = opt.len() as u32 + 2;
1324 let msg = format_message(
1325 diagnostic_messages::OPTION_CAN_ONLY_BE_USED_WHEN_MODULERESOLUTION_IS_SET_TO_NODE16_NODENEXT_OR_BUNDL,
1326 &[opt],
1327 );
1328 diagnostics.push(Diagnostic::error(
1329 file_path,
1330 start,
1331 key_len,
1332 msg,
1333 diagnostic_codes::OPTION_CAN_ONLY_BE_USED_WHEN_MODULERESOLUTION_IS_SET_TO_NODE16_NODENEXT_OR_BUNDL,
1334 ));
1335 }
1336 }
1337 }
1338 }
1339
1340 let config: TsConfig = serde_json::from_value(raw).context("failed to parse tsconfig JSON")?;
1341
1342 Ok(ParsedTsConfig {
1343 config,
1344 diagnostics,
1345 suppress_excess_property_errors: suppress_excess,
1346 suppress_implicit_any_index_errors: suppress_any_index,
1347 })
1348}
1349
1350const fn option_is_truthy(value: Option<&serde_json::Value>) -> bool {
1354 match value {
1355 None | Some(serde_json::Value::Null) => false,
1356 Some(serde_json::Value::Bool(b)) => *b,
1357 Some(_) => true,
1359 }
1360}
1361
1362fn find_key_offset_in_source(source: &str, key: &str) -> u32 {
1365 let search = format!("\"{key}\"");
1366 let compiler_opts_pos = source.find("compilerOptions").unwrap_or(0);
1368 if let Some(pos) = source[compiler_opts_pos..].find(&search) {
1369 (compiler_opts_pos + pos) as u32
1371 } else {
1372 0
1373 }
1374}
1375
1376fn find_value_offset_in_source(source: &str, key: &str) -> u32 {
1379 let search = format!("\"{key}\"");
1380 let compiler_opts_pos = source.find("compilerOptions").unwrap_or(0);
1381 if let Some(key_pos) = source[compiler_opts_pos..].find(&search) {
1382 let after_key = compiler_opts_pos + key_pos + search.len();
1383 let rest = &source[after_key..];
1385 if let Some(colon_pos) = rest.find(':') {
1386 let after_colon = after_key + colon_pos + 1;
1387 let value_rest = &source[after_colon..];
1388 let trimmed_offset = value_rest.len() - value_rest.trim_start().len();
1390 return (after_colon + trimmed_offset) as u32;
1391 }
1392 }
1393 0
1394}
1395
1396fn estimate_json_value_len(value: &serde_json::Value) -> u32 {
1398 match value {
1399 serde_json::Value::String(s) => s.len() as u32 + 2, serde_json::Value::Bool(b) => {
1401 if *b {
1402 4
1403 } else {
1404 5
1405 }
1406 }
1407 serde_json::Value::Number(n) => n.to_string().len() as u32,
1408 serde_json::Value::Null => 4,
1409 serde_json::Value::Array(_) | serde_json::Value::Object(_) => serde_json::to_string(value)
1410 .map(|s| s.len() as u32)
1411 .unwrap_or(2),
1412 }
1413}
1414
1415fn compiler_option_expected_type(key: &str) -> &'static str {
1418 match key {
1419 "allowArbitraryExtensions"
1421 | "allowImportingTsExtensions"
1422 | "allowJs"
1423 | "allowSyntheticDefaultImports"
1424 | "allowUmdGlobalAccess"
1425 | "allowUnreachableCode"
1426 | "allowUnusedLabels"
1427 | "alwaysStrict"
1428 | "checkJs"
1429 | "composite"
1430 | "declaration"
1431 | "declarationMap"
1432 | "disableReferencedProjectLoad"
1433 | "disableSizeLimit"
1434 | "disableSolutionSearching"
1435 | "disableSourceOfProjectReferenceRedirect"
1436 | "downlevelIteration"
1437 | "emitBOM"
1438 | "emitDeclarationOnly"
1439 | "emitDecoratorMetadata"
1440 | "esModuleInterop"
1441 | "exactOptionalPropertyTypes"
1442 | "experimentalDecorators"
1443 | "forceConsistentCasingInFileNames"
1444 | "importHelpers"
1445 | "incremental"
1446 | "inlineSourceMap"
1447 | "inlineSources"
1448 | "isolatedDeclarations"
1449 | "isolatedModules"
1450 | "keyofStringsOnly"
1451 | "noEmit"
1452 | "noEmitHelpers"
1453 | "noEmitOnError"
1454 | "noErrorTruncation"
1455 | "noFallthroughCasesInSwitch"
1456 | "noImplicitAny"
1457 | "noImplicitOverride"
1458 | "noImplicitReturns"
1459 | "noImplicitThis"
1460 | "noImplicitUseStrict"
1461 | "noLib"
1462 | "noPropertyAccessFromIndexSignature"
1463 | "noResolve"
1464 | "noStrictGenericChecks"
1465 | "noUncheckedIndexedAccess"
1466 | "noUncheckedSideEffectImports"
1467 | "noUnusedLocals"
1468 | "noUnusedParameters"
1469 | "preserveConstEnums"
1470 | "preserveSymlinks"
1471 | "preserveValueImports"
1472 | "pretty"
1473 | "removeComments"
1474 | "resolveJsonModule"
1475 | "resolvePackageJsonExports"
1476 | "resolvePackageJsonImports"
1477 | "rewriteRelativeImportExtensions"
1478 | "skipDefaultLibCheck"
1479 | "skipLibCheck"
1480 | "sourceMap"
1481 | "strict"
1482 | "strictBindCallApply"
1483 | "strictBuiltinIteratorReturn"
1484 | "strictFunctionTypes"
1485 | "strictNullChecks"
1486 | "strictPropertyInitialization"
1487 | "stripInternal"
1488 | "suppressExcessPropertyErrors"
1489 | "suppressImplicitAnyIndexErrors"
1490 | "useDefineForClassFields"
1491 | "useUnknownInCatchVariables"
1492 | "verbatimModuleSyntax" => "boolean",
1493 "baseUrl" | "charset" | "declarationDir" | "jsx" | "jsxFactory" | "jsxFragmentFactory"
1495 | "jsxImportSource" | "mapRoot" | "module" | "moduleDetection" | "moduleResolution"
1496 | "newLine" | "out" | "outDir" | "outFile" | "reactNamespace" | "rootDir"
1497 | "sourceRoot" | "target" | "tsBuildInfoFile" | "ignoreDeprecations" => "string",
1498 "lib" | "types" | "typeRoots" | "rootDirs" | "moduleSuffixes" | "customConditions" => {
1500 "list"
1501 }
1502 "paths" => "object",
1504 _ => "",
1505 }
1506}
1507
1508fn removed_compiler_option(key: &str) -> Option<&'static str> {
1512 match key {
1513 "noImplicitUseStrict"
1514 | "keyofStringsOnly"
1515 | "suppressExcessPropertyErrors"
1516 | "suppressImplicitAnyIndexErrors"
1517 | "noStrictGenericChecks"
1518 | "charset" => Some(""),
1519 "importsNotUsedAsValues" | "preserveValueImports" => Some("verbatimModuleSyntax"),
1520 "out" => Some("outFile"),
1521 _ => None,
1522 }
1523}
1524
1525fn known_compiler_option(key_lower: &str) -> Option<&'static str> {
1528 match key_lower {
1529 "allowarbitraryextensions" => Some("allowArbitraryExtensions"),
1530 "allowimportingtsextensions" => Some("allowImportingTsExtensions"),
1531 "allowjs" => Some("allowJs"),
1532 "allowsyntheticdefaultimports" => Some("allowSyntheticDefaultImports"),
1533 "allowumdglobalaccess" => Some("allowUmdGlobalAccess"),
1534 "allowunreachablecode" => Some("allowUnreachableCode"),
1535 "allowunusedlabels" => Some("allowUnusedLabels"),
1536 "alwaysstrict" => Some("alwaysStrict"),
1537 "baseurl" => Some("baseUrl"),
1538 "charset" => Some("charset"),
1539 "checkjs" => Some("checkJs"),
1540 "composite" => Some("composite"),
1541 "customconditions" => Some("customConditions"),
1542 "declaration" => Some("declaration"),
1543 "declarationdir" => Some("declarationDir"),
1544 "declarationmap" => Some("declarationMap"),
1545 "diagnostics" => Some("diagnostics"),
1546 "disablereferencedprojectload" => Some("disableReferencedProjectLoad"),
1547 "disablesizelimt" => Some("disableSizeLimit"),
1548 "disablesolutiontypecheck" => Some("disableSolutionTypeCheck"),
1549 "disablesolutioncaching" => Some("disableSolutionCaching"),
1550 "disablesolutiontypechecking" => Some("disableSolutionTypeChecking"),
1551 "disablesourceofreferencedprojectload" => Some("disableSourceOfReferencedProjectLoad"),
1552 "downleveliteration" => Some("downlevelIteration"),
1553 "emitbom" => Some("emitBOM"),
1554 "emitdeclarationonly" => Some("emitDeclarationOnly"),
1555 "emitdecoratormetadata" => Some("emitDecoratorMetadata"),
1556 "erasablesyntaxonly" => Some("erasableSyntaxOnly"),
1557 "esmoduleinterop" => Some("esModuleInterop"),
1558 "exactoptionalpropertytypes" => Some("exactOptionalPropertyTypes"),
1559 "experimentaldecorators" => Some("experimentalDecorators"),
1560 "extendeddiagnostics" => Some("extendedDiagnostics"),
1561 "forceconsecinferfaces" | "forceconsistentcasinginfilenames" => {
1562 Some("forceConsistentCasingInFileNames")
1563 }
1564 "generatecputrace" | "generatecpuprofile" => Some("generateCpuProfile"),
1565 "generatetrace" => Some("generateTrace"),
1566 "ignoredeprecations" => Some("ignoreDeprecations"),
1567 "importhelpers" => Some("importHelpers"),
1568 "importsnotusedasvalues" => Some("importsNotUsedAsValues"),
1569 "incremental" => Some("incremental"),
1570 "inlineconstants" => Some("inlineConstants"),
1571 "inlinesourcemap" => Some("inlineSourceMap"),
1572 "inlinesources" => Some("inlineSources"),
1573 "isolateddeclarations" => Some("isolatedDeclarations"),
1574 "isolatedmodules" => Some("isolatedModules"),
1575 "jsx" => Some("jsx"),
1576 "jsxfactory" => Some("jsxFactory"),
1577 "jsxfragmentfactory" => Some("jsxFragmentFactory"),
1578 "jsximportsource" => Some("jsxImportSource"),
1579 "keyofstringsonly" => Some("keyofStringsOnly"),
1580 "lib" => Some("lib"),
1581 "libreplacement" => Some("libReplacement"),
1582 "listemittedfiles" => Some("listEmittedFiles"),
1583 "listfiles" => Some("listFiles"),
1584 "listfilesonly" => Some("listFilesOnly"),
1585 "locale" => Some("locale"),
1586 "maproot" => Some("mapRoot"),
1587 "maxnodemodulejsdepth" => Some("maxNodeModuleJsDepth"),
1588 "module" => Some("module"),
1589 "moduledetection" => Some("moduleDetection"),
1590 "moduleresolution" => Some("moduleResolution"),
1591 "modulesuffixes" => Some("moduleSuffixes"),
1592 "newline" => Some("newLine"),
1593 "nocheck" => Some("noCheck"),
1594 "noemit" => Some("noEmit"),
1595 "noemithelpers" => Some("noEmitHelpers"),
1596 "noemitonerror" => Some("noEmitOnError"),
1597 "noerrortruncation" => Some("noErrorTruncation"),
1598 "nofallthroughcasesinswitch" => Some("noFallthroughCasesInSwitch"),
1599 "noimplicitany" => Some("noImplicitAny"),
1600 "noimplicitoverride" => Some("noImplicitOverride"),
1601 "noimplicitreturns" => Some("noImplicitReturns"),
1602 "noimplicitthis" => Some("noImplicitThis"),
1603 "noimplicitusestrict" => Some("noImplicitUseStrict"),
1604 "nolib" => Some("noLib"),
1605 "nopropertyaccessfromindexsignature" => Some("noPropertyAccessFromIndexSignature"),
1606 "noresolve" => Some("noResolve"),
1607 "nostrictgenericchecks" => Some("noStrictGenericChecks"),
1608 "notypesandsymbols" => Some("noTypesAndSymbols"),
1609 "nouncheckedindexedaccess" => Some("noUncheckedIndexedAccess"),
1610 "nouncheckedsideeffectimports" => Some("noUncheckedSideEffectImports"),
1611 "nounusedlocals" => Some("noUnusedLocals"),
1612 "nounusedparameters" => Some("noUnusedParameters"),
1613 "out" => Some("out"),
1614 "outdir" => Some("outDir"),
1615 "outfile" => Some("outFile"),
1616 "paths" => Some("paths"),
1617 "plugins" => Some("plugins"),
1618 "preserveconstenums" => Some("preserveConstEnums"),
1619 "preservesymlinks" => Some("preserveSymlinks"),
1620 "preservevalueimports" => Some("preserveValueImports"),
1621 "preservewatchoutput" => Some("preserveWatchOutput"),
1622 "pretty" => Some("pretty"),
1623 "reactnamespace" => Some("reactNamespace"),
1624 "removecomments" => Some("removeComments"),
1625 "resolvejsonmodule" => Some("resolveJsonModule"),
1626 "resolvepackagejsonexports" => Some("resolvePackageJsonExports"),
1627 "resolvepackagejsonimports" => Some("resolvePackageJsonImports"),
1628 "rewriterelativeimportextensions" => Some("rewriteRelativeImportExtensions"),
1629 "rootdir" => Some("rootDir"),
1630 "rootdirs" => Some("rootDirs"),
1631 "skipdefaultlibcheck" => Some("skipDefaultLibCheck"),
1632 "skiplibcheck" => Some("skipLibCheck"),
1633 "sourcemap" => Some("sourceMap"),
1634 "sourceroot" => Some("sourceRoot"),
1635 "strict" => Some("strict"),
1636 "strictbindcallapply" => Some("strictBindCallApply"),
1637 "strictbuiltiniteratorreturn" => Some("strictBuiltinIteratorReturn"),
1638 "strictfunctiontypes" => Some("strictFunctionTypes"),
1639 "strictnullchecks" => Some("strictNullChecks"),
1640 "strictpropertyinitialization" => Some("strictPropertyInitialization"),
1641 "stripinternal" => Some("stripInternal"),
1642 "suppressexcesspropertyerrors" => Some("suppressExcessPropertyErrors"),
1643 "suppressimplicitanyindexerrors" => Some("suppressImplicitAnyIndexErrors"),
1644 "target" => Some("target"),
1645 "traceresolution" => Some("traceResolution"),
1646 "tsbuildinfofile" => Some("tsBuildInfoFile"),
1647 "typeroots" => Some("typeRoots"),
1648 "types" => Some("types"),
1649 "usedefineforclassfields" => Some("useDefineForClassFields"),
1650 "useunknownincatchvariables" => Some("useUnknownInCatchVariables"),
1651 "verbatimmodulesyntax" => Some("verbatimModuleSyntax"),
1652 _ => None,
1653 }
1654}
1655
1656pub fn load_tsconfig(path: &Path) -> Result<TsConfig> {
1657 let mut visited = FxHashSet::default();
1658 load_tsconfig_inner(path, &mut visited)
1659}
1660
1661pub fn load_tsconfig_with_diagnostics(path: &Path) -> Result<ParsedTsConfig> {
1663 let mut visited = FxHashSet::default();
1664 load_tsconfig_inner_with_diagnostics(path, &mut visited)
1665}
1666
1667fn load_tsconfig_inner(path: &Path, visited: &mut FxHashSet<PathBuf>) -> Result<TsConfig> {
1668 let canonical = std::fs::canonicalize(path).unwrap_or_else(|_| path.to_path_buf());
1669 if !visited.insert(canonical.clone()) {
1670 bail!("tsconfig extends cycle detected at {}", canonical.display());
1671 }
1672
1673 let source = std::fs::read_to_string(path)
1674 .with_context(|| format!("failed to read tsconfig: {}", path.display()))?;
1675 let mut config = parse_tsconfig(&source)
1676 .with_context(|| format!("failed to parse tsconfig: {}", path.display()))?;
1677
1678 let extends = config.extends.take();
1679 if let Some(extends_path) = extends {
1680 let base_path = resolve_extends_path(path, &extends_path)?;
1681 let base_config = load_tsconfig_inner(&base_path, visited)?;
1682 config = merge_configs(base_config, config);
1683 }
1684
1685 visited.remove(&canonical);
1686 Ok(config)
1687}
1688
1689fn load_tsconfig_inner_with_diagnostics(
1690 path: &Path,
1691 visited: &mut FxHashSet<PathBuf>,
1692) -> Result<ParsedTsConfig> {
1693 let canonical = std::fs::canonicalize(path).unwrap_or_else(|_| path.to_path_buf());
1694 if !visited.insert(canonical.clone()) {
1695 bail!("tsconfig extends cycle detected at {}", canonical.display());
1696 }
1697
1698 let source = std::fs::read_to_string(path)
1699 .with_context(|| format!("failed to read tsconfig: {}", path.display()))?;
1700 let file_display = path.display().to_string();
1701 let mut parsed = parse_tsconfig_with_diagnostics(&source, &file_display)
1702 .with_context(|| format!("failed to parse tsconfig: {}", path.display()))?;
1703
1704 let extends = parsed.config.extends.take();
1705 if let Some(extends_path) = extends {
1706 let base_path = resolve_extends_path(path, &extends_path)?;
1707 let base_config = load_tsconfig_inner(&base_path, visited)?;
1708 parsed.config = merge_configs(base_config, parsed.config);
1709 }
1710
1711 visited.remove(&canonical);
1712 Ok(parsed)
1713}
1714
1715fn resolve_extends_path(current_path: &Path, extends: &str) -> Result<PathBuf> {
1716 let base_dir = current_path
1717 .parent()
1718 .ok_or_else(|| anyhow!("tsconfig has no parent directory"))?;
1719 let mut candidate = PathBuf::from(extends);
1720 if candidate.extension().is_none() {
1721 candidate.set_extension("json");
1722 }
1723
1724 if candidate.is_absolute() {
1725 Ok(candidate)
1726 } else {
1727 Ok(base_dir.join(candidate))
1728 }
1729}
1730
1731fn merge_configs(base: TsConfig, mut child: TsConfig) -> TsConfig {
1732 let merged_compiler_options = match (base.compiler_options, child.compiler_options.take()) {
1733 (Some(base_opts), Some(child_opts)) => Some(merge_compiler_options(base_opts, child_opts)),
1734 (Some(base_opts), None) => Some(base_opts),
1735 (None, Some(child_opts)) => Some(child_opts),
1736 (None, None) => None,
1737 };
1738
1739 TsConfig {
1740 extends: None,
1741 compiler_options: merged_compiler_options,
1742 include: child.include.or(base.include),
1743 exclude: child.exclude.or(base.exclude),
1744 files: child.files.or(base.files),
1745 }
1746}
1747
1748macro_rules! merge_options {
1751 ($child:expr, $base:expr, $Struct:ident { $($field:ident),* $(,)? }) => {
1752 $Struct { $( $field: $child.$field.or($base.$field), )* }
1753 };
1754}
1755
1756fn merge_compiler_options(base: CompilerOptions, child: CompilerOptions) -> CompilerOptions {
1757 merge_options!(
1758 child,
1759 base,
1760 CompilerOptions {
1761 target,
1762 module,
1763 module_resolution,
1764 resolve_package_json_exports,
1765 resolve_package_json_imports,
1766 module_suffixes,
1767 resolve_json_module,
1768 allow_arbitrary_extensions,
1769 allow_importing_ts_extensions,
1770 rewrite_relative_import_extensions,
1771 types_versions_compiler_version,
1772 types,
1773 type_roots,
1774 jsx,
1775 jsx_factory,
1776 jsx_fragment_factory,
1777 react_namespace,
1778
1779 lib,
1780 no_lib,
1781 no_types_and_symbols,
1782 base_url,
1783 paths,
1784 root_dir,
1785 out_dir,
1786 out_file,
1787 declaration,
1788 declaration_dir,
1789 source_map,
1790 declaration_map,
1791 ts_build_info_file,
1792 incremental,
1793 strict,
1794 no_emit,
1795 no_emit_on_error,
1796 isolated_modules,
1797 custom_conditions,
1798 es_module_interop,
1799 allow_synthetic_default_imports,
1800 experimental_decorators,
1801 import_helpers,
1802 allow_js,
1803 check_js,
1804 skip_lib_check,
1805 always_strict,
1806 use_define_for_class_fields,
1807 no_implicit_any,
1808 no_implicit_returns,
1809 strict_null_checks,
1810 strict_function_types,
1811 strict_property_initialization,
1812 no_implicit_this,
1813 use_unknown_in_catch_variables,
1814 strict_bind_call_apply,
1815 no_unchecked_indexed_access,
1816 no_unused_locals,
1817 no_unused_parameters,
1818 allow_unreachable_code,
1819 no_resolve,
1820 no_unchecked_side_effect_imports,
1821 no_implicit_override,
1822 module_detection,
1823 }
1824 )
1825}
1826
1827fn parse_script_target(value: &str) -> Result<ScriptTarget> {
1828 let cleaned = value.trim_end_matches(',');
1831 let normalized = normalize_option(cleaned);
1832 let target = match normalized.as_str() {
1833 "es3" => ScriptTarget::ES3,
1834 "es5" => ScriptTarget::ES5,
1835 "es6" | "es2015" => ScriptTarget::ES2015,
1836 "es2016" => ScriptTarget::ES2016,
1837 "es2017" => ScriptTarget::ES2017,
1838 "es2018" => ScriptTarget::ES2018,
1839 "es2019" => ScriptTarget::ES2019,
1840 "es2020" => ScriptTarget::ES2020,
1841 "es2021" => ScriptTarget::ES2021,
1842 "es2022" | "es2023" | "es2024" => ScriptTarget::ES2022,
1843 "esnext" => ScriptTarget::ESNext,
1844 _ => bail!("unsupported compilerOptions.target '{value}'"),
1845 };
1846
1847 Ok(target)
1848}
1849
1850fn parse_module_kind(value: &str) -> Result<ModuleKind> {
1851 let cleaned = value.split(',').next().unwrap_or(value).trim();
1852 let normalized = normalize_option(cleaned);
1853 let module = match normalized.as_str() {
1854 "none" => ModuleKind::None,
1855 "commonjs" => ModuleKind::CommonJS,
1856 "amd" => ModuleKind::AMD,
1857 "umd" => ModuleKind::UMD,
1858 "system" => ModuleKind::System,
1859 "es6" | "es2015" => ModuleKind::ES2015,
1860 "es2020" => ModuleKind::ES2020,
1861 "es2022" => ModuleKind::ES2022,
1862 "esnext" => ModuleKind::ESNext,
1863 "node16" | "node18" | "node20" => ModuleKind::Node16,
1864 "nodenext" => ModuleKind::NodeNext,
1865 "preserve" => ModuleKind::Preserve,
1866 _ => bail!("unsupported compilerOptions.module '{value}'"),
1867 };
1868
1869 Ok(module)
1870}
1871
1872fn parse_module_resolution(value: &str) -> Result<ModuleResolutionKind> {
1873 let cleaned = value.split(',').next().unwrap_or(value).trim();
1874 let normalized = normalize_option(cleaned);
1875 let resolution = match normalized.as_str() {
1876 "classic" => ModuleResolutionKind::Classic,
1877 "node" | "node10" => ModuleResolutionKind::Node,
1878 "node16" => ModuleResolutionKind::Node16,
1879 "nodenext" => ModuleResolutionKind::NodeNext,
1880 "bundler" => ModuleResolutionKind::Bundler,
1881 _ => bail!("unsupported compilerOptions.moduleResolution '{value}'"),
1882 };
1883
1884 Ok(resolution)
1885}
1886
1887fn parse_jsx_emit(value: &str) -> Result<JsxEmit> {
1888 let normalized = normalize_option(value);
1889 let jsx = match normalized.as_str() {
1890 "preserve" => JsxEmit::Preserve,
1891 "react" => JsxEmit::React,
1892 "react-jsx" | "reactjsx" => JsxEmit::ReactJsx,
1893 "react-jsxdev" | "reactjsxdev" => JsxEmit::ReactJsxDev,
1894 "reactnative" | "react-native" => JsxEmit::ReactNative,
1895 _ => bail!("unsupported compilerOptions.jsx '{value}'"),
1896 };
1897
1898 Ok(jsx)
1899}
1900
1901const fn jsx_emit_to_mode(emit: JsxEmit) -> tsz_common::checker_options::JsxMode {
1902 use tsz_common::checker_options::JsxMode;
1903 match emit {
1904 JsxEmit::Preserve => JsxMode::Preserve,
1905 JsxEmit::React => JsxMode::React,
1906 JsxEmit::ReactJsx => JsxMode::ReactJsx,
1907 JsxEmit::ReactJsxDev => JsxMode::ReactJsxDev,
1908 JsxEmit::ReactNative => JsxMode::ReactNative,
1909 }
1910}
1911
1912fn build_path_mappings(paths: &FxHashMap<String, Vec<String>>) -> Vec<PathMapping> {
1913 let mut mappings = Vec::new();
1914 for (pattern, targets) in paths {
1915 if targets.is_empty() {
1916 continue;
1917 }
1918 let pattern = normalize_path_pattern(pattern);
1919 let targets = targets
1920 .iter()
1921 .map(|target| normalize_path_pattern(target))
1922 .collect();
1923 let (prefix, suffix) = split_path_pattern(&pattern);
1924 mappings.push(PathMapping {
1925 pattern,
1926 prefix,
1927 suffix,
1928 targets,
1929 });
1930 }
1931 mappings.sort_by(|left, right| {
1932 right
1933 .specificity()
1934 .cmp(&left.specificity())
1935 .then_with(|| right.pattern.len().cmp(&left.pattern.len()))
1936 .then_with(|| left.pattern.cmp(&right.pattern))
1937 });
1938 mappings
1939}
1940
1941fn normalize_path_pattern(value: &str) -> String {
1942 value.trim().replace('\\', "/")
1943}
1944
1945fn split_path_pattern(pattern: &str) -> (String, String) {
1946 match pattern.find('*') {
1947 Some(star_idx) => {
1948 let (prefix, rest) = pattern.split_at(star_idx);
1949 (prefix.to_string(), rest[1..].to_string())
1950 }
1951 None => (pattern.to_string(), String::new()),
1952 }
1953}
1954
1955pub fn resolve_lib_files_with_options(
1966 lib_list: &[String],
1967 follow_references: bool,
1968) -> Result<Vec<PathBuf>> {
1969 if lib_list.is_empty() {
1970 return Ok(Vec::new());
1971 }
1972
1973 let lib_dir = default_lib_dir()?;
1974 resolve_lib_files_from_dir_with_options(lib_list, follow_references, &lib_dir)
1975}
1976
1977pub fn resolve_lib_files_from_dir_with_options(
1978 lib_list: &[String],
1979 follow_references: bool,
1980 lib_dir: &Path,
1981) -> Result<Vec<PathBuf>> {
1982 if lib_list.is_empty() {
1983 return Ok(Vec::new());
1984 }
1985
1986 let lib_map = build_lib_map(lib_dir)?;
1987 let mut resolved = Vec::new();
1988 let mut pending: VecDeque<String> = lib_list
1989 .iter()
1990 .map(|value| normalize_lib_name(value))
1991 .collect();
1992 let mut visited = FxHashSet::default();
1993
1994 while let Some(lib_name) = pending.pop_front() {
1995 if lib_name.is_empty() || !visited.insert(lib_name.clone()) {
1996 continue;
1997 }
1998
1999 let path = match lib_map.get(&lib_name) {
2000 Some(path) => path.clone(),
2001 None => {
2002 let alias = match lib_name.as_str() {
2007 "lib" => Some("es5.full"),
2008 "es6" => Some("es2015.full"),
2009 "es7" => Some("es2016"),
2010 _ => None,
2011 };
2012 let Some(alias) = alias else {
2013 return Err(anyhow!(
2014 "unsupported compilerOptions.lib '{}' (not found in {})",
2015 lib_name,
2016 lib_dir.display()
2017 ));
2018 };
2019 lib_map.get(alias).cloned().ok_or_else(|| {
2020 anyhow!(
2021 "unsupported compilerOptions.lib '{}' (alias '{}' not found in {})",
2022 lib_name,
2023 alias,
2024 lib_dir.display()
2025 )
2026 })?
2027 }
2028 };
2029 resolved.push(path.clone());
2030
2031 if follow_references {
2033 let contents = std::fs::read_to_string(&path)
2034 .with_context(|| format!("failed to read lib file {}", path.display()))?;
2035 for reference in extract_lib_references(&contents) {
2036 pending.push_back(reference);
2037 }
2038 }
2039 }
2040
2041 Ok(resolved)
2042}
2043
2044pub fn resolve_lib_files(lib_list: &[String]) -> Result<Vec<PathBuf>> {
2047 resolve_lib_files_with_options(lib_list, true)
2048}
2049
2050pub fn resolve_lib_files_from_dir(lib_list: &[String], lib_dir: &Path) -> Result<Vec<PathBuf>> {
2051 resolve_lib_files_from_dir_with_options(lib_list, true, lib_dir)
2052}
2053
2054pub fn resolve_default_lib_files(target: ScriptTarget) -> Result<Vec<PathBuf>> {
2063 let lib_dir = default_lib_dir()?;
2064 resolve_default_lib_files_from_dir(target, &lib_dir)
2065}
2066
2067pub fn resolve_default_lib_files_from_dir(
2068 target: ScriptTarget,
2069 lib_dir: &Path,
2070) -> Result<Vec<PathBuf>> {
2071 let root_lib = default_lib_name_for_target(target);
2072 resolve_lib_files_from_dir(&[root_lib.to_string()], lib_dir)
2073}
2074
2075pub const fn default_lib_name_for_target(target: ScriptTarget) -> &'static str {
2092 match target {
2093 ScriptTarget::ES3 | ScriptTarget::ES5 => "lib",
2095 ScriptTarget::ES2015 => "es6",
2098 ScriptTarget::ES2016 => "es2016.full",
2100 ScriptTarget::ES2017 => "es2017.full",
2101 ScriptTarget::ES2018 => "es2018.full",
2102 ScriptTarget::ES2019 => "es2019.full",
2103 ScriptTarget::ES2020 => "es2020.full",
2104 ScriptTarget::ES2021 => "es2021.full",
2105 ScriptTarget::ES2022 => "es2022.full",
2106 ScriptTarget::ES2023
2107 | ScriptTarget::ES2024
2108 | ScriptTarget::ES2025
2109 | ScriptTarget::ESNext => "esnext.full",
2110 }
2111}
2112
2113pub const fn core_lib_name_for_target(target: ScriptTarget) -> &'static str {
2120 match target {
2121 ScriptTarget::ES3 | ScriptTarget::ES5 => "es5",
2122 ScriptTarget::ES2015 => "es2015",
2123 ScriptTarget::ES2016 => "es2016",
2124 ScriptTarget::ES2017 => "es2017",
2125 ScriptTarget::ES2018 => "es2018",
2126 ScriptTarget::ES2019 => "es2019",
2127 ScriptTarget::ES2020 => "es2020",
2128 ScriptTarget::ES2021 => "es2021",
2129 ScriptTarget::ES2022 => "es2022",
2130 ScriptTarget::ES2023
2131 | ScriptTarget::ES2024
2132 | ScriptTarget::ES2025
2133 | ScriptTarget::ESNext => "esnext",
2134 }
2135}
2136
2137pub fn default_lib_dir() -> Result<PathBuf> {
2145 if let Some(dir) = env::var_os("TSZ_LIB_DIR") {
2146 let dir = PathBuf::from(dir);
2147 if !dir.is_dir() {
2148 bail!(
2149 "TSZ_LIB_DIR does not point to a directory: {}",
2150 dir.display()
2151 );
2152 }
2153 return Ok(canonicalize_or_owned(&dir));
2154 }
2155
2156 if let Some(dir) = lib_dir_from_exe() {
2157 return Ok(dir);
2158 }
2159
2160 if let Some(dir) = lib_dir_from_cwd() {
2161 return Ok(dir);
2162 }
2163
2164 let manifest_dir = Path::new(env!("CARGO_MANIFEST_DIR"));
2165 if let Some(dir) = lib_dir_from_root(manifest_dir) {
2166 return Ok(dir);
2167 }
2168
2169 bail!("lib directory not found under {}", manifest_dir.display());
2170}
2171
2172fn lib_dir_from_exe() -> Option<PathBuf> {
2173 let exe = env::current_exe().ok()?;
2174 let exe_dir = exe.parent()?;
2175 let candidate = exe_dir.join("lib");
2176 if candidate.is_dir() {
2177 return Some(canonicalize_or_owned(&candidate));
2178 }
2179 lib_dir_from_root(exe_dir)
2180}
2181
2182fn lib_dir_from_cwd() -> Option<PathBuf> {
2183 let cwd = env::current_dir().ok()?;
2184 lib_dir_from_root(&cwd)
2185}
2186
2187fn lib_dir_from_root(root: &Path) -> Option<PathBuf> {
2188 let candidates = [
2189 root.join("TypeScript").join("built").join("local"),
2191 root.join("TypeScript").join("lib"),
2192 root.join("node_modules").join("typescript").join("lib"),
2197 root.join("scripts")
2198 .join("node_modules")
2199 .join("typescript")
2200 .join("lib"),
2201 root.join("scripts")
2202 .join("emit")
2203 .join("node_modules")
2204 .join("typescript")
2205 .join("lib"),
2206 root.join("TypeScript").join("src").join("lib"),
2207 root.join("TypeScript")
2208 .join("node_modules")
2209 .join("typescript")
2210 .join("lib"),
2211 root.join("tests").join("lib"),
2212 ];
2213
2214 for candidate in candidates {
2215 if candidate.is_dir() {
2216 return Some(canonicalize_or_owned(&candidate));
2217 }
2218 }
2219
2220 None
2221}
2222
2223fn build_lib_map(lib_dir: &Path) -> Result<FxHashMap<String, PathBuf>> {
2224 let mut map = FxHashMap::default();
2225 for entry in std::fs::read_dir(lib_dir)
2226 .with_context(|| format!("failed to read lib directory {}", lib_dir.display()))?
2227 {
2228 let entry = entry?;
2229 let path = entry.path();
2230 let Some(file_name) = path.file_name().and_then(|name| name.to_str()) else {
2231 continue;
2232 };
2233 if !file_name.ends_with(".d.ts") {
2234 continue;
2235 }
2236
2237 let stem = file_name.trim_end_matches(".d.ts");
2238 let stem = stem.strip_suffix(".generated").unwrap_or(stem);
2239 let key = normalize_lib_name(stem);
2240 map.insert(key, canonicalize_or_owned(&path));
2241 }
2242
2243 Ok(map)
2244}
2245
2246pub(crate) fn extract_lib_references(source: &str) -> Vec<String> {
2249 let mut refs = Vec::new();
2250 let mut in_block_comment = false;
2251 for line in source.lines() {
2252 let line = line.trim_start();
2253 if in_block_comment {
2254 if line.contains("*/") {
2255 in_block_comment = false;
2256 }
2257 continue;
2258 }
2259 if line.starts_with("/*") {
2260 if !line.contains("*/") {
2261 in_block_comment = true;
2262 }
2263 continue;
2264 }
2265 if line.is_empty() {
2266 continue;
2267 }
2268 if line.starts_with("///") {
2269 if let Some(value) = parse_reference_lib_value(line) {
2270 refs.push(normalize_lib_name(value));
2271 }
2272 continue;
2273 }
2274 if line.starts_with("//") {
2275 continue;
2276 }
2277 break;
2278 }
2279 refs
2280}
2281
2282fn parse_reference_lib_value(line: &str) -> Option<&str> {
2283 let mut offset = 0;
2284 let bytes = line.as_bytes();
2285 while let Some(idx) = line[offset..].find("lib=") {
2286 let start = offset + idx;
2287 if start > 0 {
2288 let prev = bytes[start - 1];
2289 if !prev.is_ascii_whitespace() && prev != b'<' {
2290 offset = start + 4;
2291 continue;
2292 }
2293 }
2294 let quote = *bytes.get(start + 4)?;
2295 if quote != b'"' && quote != b'\'' {
2296 offset = start + 4;
2297 continue;
2298 }
2299 let rest = &line[start + 5..];
2300 let end = rest.find(quote as char)?;
2301 return Some(&rest[..end]);
2302 }
2303 None
2304}
2305
2306fn normalize_lib_name(value: &str) -> String {
2307 let normalized = value.trim().to_ascii_lowercase();
2308 normalized
2309 .strip_prefix("lib.")
2310 .unwrap_or(normalized.as_str())
2311 .to_string()
2312}
2313
2314pub const fn checker_target_from_emitter(target: ScriptTarget) -> CheckerScriptTarget {
2317 match target {
2318 ScriptTarget::ES3 => CheckerScriptTarget::ES3,
2319 ScriptTarget::ES5 => CheckerScriptTarget::ES5,
2320 ScriptTarget::ES2015 => CheckerScriptTarget::ES2015,
2321 ScriptTarget::ES2016 => CheckerScriptTarget::ES2016,
2322 ScriptTarget::ES2017 => CheckerScriptTarget::ES2017,
2323 ScriptTarget::ES2018 => CheckerScriptTarget::ES2018,
2324 ScriptTarget::ES2019 => CheckerScriptTarget::ES2019,
2325 ScriptTarget::ES2020 => CheckerScriptTarget::ES2020,
2326 ScriptTarget::ES2021
2327 | ScriptTarget::ES2022
2328 | ScriptTarget::ES2023
2329 | ScriptTarget::ES2024
2330 | ScriptTarget::ES2025
2331 | ScriptTarget::ESNext => CheckerScriptTarget::ESNext,
2332 }
2333}
2334
2335fn canonicalize_or_owned(path: &Path) -> PathBuf {
2336 std::fs::canonicalize(path).unwrap_or_else(|_| path.to_path_buf())
2337}
2338
2339fn normalize_option(value: &str) -> String {
2340 let mut normalized = String::with_capacity(value.len());
2341 for ch in value.chars() {
2342 if ch == '-' || ch == '_' || ch.is_whitespace() {
2343 continue;
2344 }
2345 normalized.push(ch.to_ascii_lowercase());
2346 }
2347 normalized
2348}
2349
2350fn strip_jsonc(input: &str) -> String {
2351 let mut out = String::with_capacity(input.len());
2352 let mut chars = input.chars().peekable();
2353 let mut in_string = false;
2354 let mut escape = false;
2355 let mut in_line_comment = false;
2356 let mut in_block_comment = false;
2357
2358 while let Some(ch) = chars.next() {
2359 if in_line_comment {
2360 if ch == '\n' {
2361 in_line_comment = false;
2362 out.push(ch);
2363 }
2364 continue;
2365 }
2366
2367 if in_block_comment {
2368 if ch == '*' {
2369 if let Some('/') = chars.peek().copied() {
2370 chars.next();
2371 in_block_comment = false;
2372 }
2373 } else if ch == '\n' {
2374 out.push(ch);
2375 }
2376 continue;
2377 }
2378
2379 if in_string {
2380 out.push(ch);
2381 if escape {
2382 escape = false;
2383 } else if ch == '\\' {
2384 escape = true;
2385 } else if ch == '"' {
2386 in_string = false;
2387 }
2388 continue;
2389 }
2390
2391 if ch == '"' {
2392 in_string = true;
2393 out.push(ch);
2394 continue;
2395 }
2396
2397 if ch == '/'
2398 && let Some(&next) = chars.peek()
2399 {
2400 if next == '/' {
2401 chars.next();
2402 in_line_comment = true;
2403 continue;
2404 }
2405 if next == '*' {
2406 chars.next();
2407 in_block_comment = true;
2408 continue;
2409 }
2410 }
2411
2412 out.push(ch);
2413 }
2414
2415 out
2416}
2417
2418fn remove_trailing_commas(input: &str) -> String {
2419 let mut out = String::with_capacity(input.len());
2420 let mut chars = input.chars().peekable();
2421 let mut in_string = false;
2422 let mut escape = false;
2423
2424 while let Some(ch) = chars.next() {
2425 if in_string {
2426 out.push(ch);
2427 if escape {
2428 escape = false;
2429 } else if ch == '\\' {
2430 escape = true;
2431 } else if ch == '"' {
2432 in_string = false;
2433 }
2434 continue;
2435 }
2436
2437 if ch == '"' {
2438 in_string = true;
2439 out.push(ch);
2440 continue;
2441 }
2442
2443 if ch == ',' {
2444 let mut lookahead = chars.clone();
2445 while let Some(next) = lookahead.peek().copied() {
2446 if next.is_whitespace() {
2447 lookahead.next();
2448 continue;
2449 }
2450 if next == '}' || next == ']' {
2451 break;
2452 }
2453 break;
2454 }
2455
2456 if let Some(next) = lookahead.peek().copied()
2457 && (next == '}' || next == ']')
2458 {
2459 continue;
2460 }
2461 }
2462
2463 out.push(ch);
2464 }
2465
2466 out
2467}
2468
2469#[cfg(test)]
2470mod tests {
2471 use super::*;
2472
2473 #[test]
2474 fn test_parse_boolean_true() {
2475 let json = r#"{"strict": true}"#;
2476 let opts: CompilerOptions = serde_json::from_str(json).unwrap();
2477 assert_eq!(opts.strict, Some(true));
2478 }
2479
2480 #[test]
2481 fn test_parse_string_true() {
2482 let json = r#"{"strict": "true"}"#;
2483 let opts: CompilerOptions = serde_json::from_str(json).unwrap();
2484 assert_eq!(opts.strict, Some(true));
2485 }
2486
2487 #[test]
2488 fn test_parse_invalid_string() {
2489 let json = r#"{"strict": "invalid"}"#;
2490 let result: Result<CompilerOptions, _> = serde_json::from_str(json);
2491 assert!(result.is_err());
2492 }
2493
2494 #[test]
2495 fn test_parse_module_resolution_list_value() {
2496 let json =
2497 r#"{"compilerOptions":{"moduleResolution":"node16,nodenext","module":"commonjs"}} "#;
2498 let config: TsConfig = serde_json::from_str(json).unwrap();
2499 let resolved = resolve_compiler_options(config.compiler_options.as_ref()).unwrap();
2500 assert_eq!(
2501 resolved.module_resolution,
2502 Some(ModuleResolutionKind::Node16)
2503 );
2504 }
2505
2506 #[test]
2507 fn test_module_explicitly_set_when_specified() {
2508 let json = r#"{"compilerOptions":{"module":"es2015"}}"#;
2509 let config: TsConfig = serde_json::from_str(json).unwrap();
2510 let resolved = resolve_compiler_options(config.compiler_options.as_ref()).unwrap();
2511 assert!(resolved.checker.module_explicitly_set);
2512 assert!(resolved.checker.module.is_es_module());
2513 }
2514
2515 #[test]
2516 fn test_module_explicitly_set_commonjs() {
2517 let json = r#"{"compilerOptions":{"module":"commonjs"}}"#;
2518 let config: TsConfig = serde_json::from_str(json).unwrap();
2519 let resolved = resolve_compiler_options(config.compiler_options.as_ref()).unwrap();
2520 assert!(resolved.checker.module_explicitly_set);
2521 assert!(!resolved.checker.module.is_es_module());
2522 }
2523
2524 #[test]
2525 fn test_module_not_explicitly_set_defaults_from_target() {
2526 let json = r#"{"compilerOptions":{"target":"es2015"}}"#;
2529 let config: TsConfig = serde_json::from_str(json).unwrap();
2530 let resolved = resolve_compiler_options(config.compiler_options.as_ref()).unwrap();
2531 assert!(!resolved.checker.module_explicitly_set);
2532 assert!(resolved.checker.module.is_es_module());
2534 }
2535
2536 #[test]
2537 fn test_module_not_explicitly_set_no_options() {
2538 let resolved = resolve_compiler_options(None).unwrap();
2540 assert!(!resolved.checker.module_explicitly_set);
2541 }
2542
2543 #[test]
2544 fn test_removed_compiler_option_lookup() {
2545 assert!(removed_compiler_option("noImplicitUseStrict").is_some());
2546 assert!(removed_compiler_option("keyofStringsOnly").is_some());
2547 assert!(removed_compiler_option("suppressExcessPropertyErrors").is_some());
2548 assert!(removed_compiler_option("suppressImplicitAnyIndexErrors").is_some());
2549 assert!(removed_compiler_option("noStrictGenericChecks").is_some());
2550 assert!(removed_compiler_option("charset").is_some());
2551 assert!(removed_compiler_option("out").is_some());
2552 assert_eq!(
2553 removed_compiler_option("importsNotUsedAsValues"),
2554 Some("verbatimModuleSyntax")
2555 );
2556 assert_eq!(
2557 removed_compiler_option("preserveValueImports"),
2558 Some("verbatimModuleSyntax")
2559 );
2560 assert!(removed_compiler_option("strict").is_none());
2562 assert!(removed_compiler_option("target").is_none());
2563 }
2564
2565 #[test]
2566 fn test_ts5102_emitted_for_removed_option() {
2567 let source = r#"{"compilerOptions":{"noImplicitUseStrict":true}}"#;
2568 let parsed = parse_tsconfig_with_diagnostics(source, "tsconfig.json").unwrap();
2569 let codes: Vec<u32> = parsed.diagnostics.iter().map(|d| d.code).collect();
2570 assert!(
2571 codes.contains(&5102),
2572 "Expected TS5102 for removed option noImplicitUseStrict, got: {codes:?}"
2573 );
2574 }
2575
2576 #[test]
2577 fn test_ts5102_not_emitted_for_false_removed_option() {
2578 let source = r#"{"compilerOptions":{"noImplicitUseStrict":false}}"#;
2580 let parsed = parse_tsconfig_with_diagnostics(source, "tsconfig.json").unwrap();
2581 let codes: Vec<u32> = parsed.diagnostics.iter().map(|d| d.code).collect();
2582 assert!(
2583 !codes.contains(&5102),
2584 "Should NOT emit TS5102 for false-valued removed option, got: {codes:?}"
2585 );
2586 }
2587
2588 #[test]
2589 fn test_ts5102_emitted_for_string_removed_option() {
2590 let source = r#"{"compilerOptions":{"importsNotUsedAsValues":"error"}}"#;
2591 let parsed = parse_tsconfig_with_diagnostics(source, "tsconfig.json").unwrap();
2592 let codes: Vec<u32> = parsed.diagnostics.iter().map(|d| d.code).collect();
2593 assert!(
2594 codes.contains(&5102),
2595 "Expected TS5102 for removed option importsNotUsedAsValues, got: {codes:?}"
2596 );
2597 }
2598
2599 #[test]
2600 fn test_ts5102_suppressed_with_ignore_deprecations() {
2601 let source =
2603 r#"{"compilerOptions":{"ignoreDeprecations":"5.0","noImplicitUseStrict":true}}"#;
2604 let parsed = parse_tsconfig_with_diagnostics(source, "tsconfig.json").unwrap();
2605 let codes: Vec<u32> = parsed.diagnostics.iter().map(|d| d.code).collect();
2606 assert!(
2607 !codes.contains(&5102),
2608 "Should NOT emit TS5102 when ignoreDeprecations is '5.0', got: {codes:?}"
2609 );
2610 }
2611
2612 #[test]
2613 fn test_ts5102_not_suppressed_with_invalid_ignore_deprecations() {
2614 let source =
2616 r#"{"compilerOptions":{"ignoreDeprecations":"6.0","noImplicitUseStrict":true}}"#;
2617 let parsed = parse_tsconfig_with_diagnostics(source, "tsconfig.json").unwrap();
2618 let codes: Vec<u32> = parsed.diagnostics.iter().map(|d| d.code).collect();
2619 assert!(
2620 codes.contains(&5102),
2621 "Should emit TS5102 when ignoreDeprecations is invalid, got: {codes:?}"
2622 );
2623 assert!(
2624 codes.contains(&5103),
2625 "Should also emit TS5103 for invalid ignoreDeprecations, got: {codes:?}"
2626 );
2627 }
2628
2629 #[test]
2630 fn test_ts5102_not_emitted_for_valid_option() {
2631 let source = r#"{"compilerOptions":{"strict":true}}"#;
2632 let parsed = parse_tsconfig_with_diagnostics(source, "tsconfig.json").unwrap();
2633 let codes: Vec<u32> = parsed.diagnostics.iter().map(|d| d.code).collect();
2634 assert!(
2635 !codes.contains(&5102),
2636 "Should NOT emit TS5102 for valid option 'strict', got: {codes:?}"
2637 );
2638 }
2639
2640 #[test]
2641 fn test_ts5095_bundler_with_commonjs() {
2642 let source = r#"{"compilerOptions":{"module":"commonjs","moduleResolution":"bundler"}}"#;
2643 let parsed = parse_tsconfig_with_diagnostics(source, "tsconfig.json").unwrap();
2644 let codes: Vec<u32> = parsed.diagnostics.iter().map(|d| d.code).collect();
2645 assert!(
2646 codes.contains(&5095),
2647 "Expected TS5095 for bundler+commonjs, got: {codes:?}"
2648 );
2649 }
2650
2651 #[test]
2652 fn test_ts5095_bundler_with_none() {
2653 let source = r#"{"compilerOptions":{"module":"none","moduleResolution":"bundler"}}"#;
2654 let parsed = parse_tsconfig_with_diagnostics(source, "tsconfig.json").unwrap();
2655 let codes: Vec<u32> = parsed.diagnostics.iter().map(|d| d.code).collect();
2656 assert!(
2657 codes.contains(&5095),
2658 "Expected TS5095 for bundler+none, got: {codes:?}"
2659 );
2660 }
2661
2662 #[test]
2663 fn test_ts5095_bundler_with_amd() {
2664 let source = r#"{"compilerOptions":{"module":"amd","moduleResolution":"bundler"}}"#;
2665 let parsed = parse_tsconfig_with_diagnostics(source, "tsconfig.json").unwrap();
2666 let codes: Vec<u32> = parsed.diagnostics.iter().map(|d| d.code).collect();
2667 assert!(
2668 codes.contains(&5095),
2669 "Expected TS5095 for bundler+amd, got: {codes:?}"
2670 );
2671 }
2672
2673 #[test]
2674 fn test_ts5095_bundler_with_system() {
2675 let source = r#"{"compilerOptions":{"module":"system","moduleResolution":"bundler"}}"#;
2676 let parsed = parse_tsconfig_with_diagnostics(source, "tsconfig.json").unwrap();
2677 let codes: Vec<u32> = parsed.diagnostics.iter().map(|d| d.code).collect();
2678 assert!(
2679 codes.contains(&5095),
2680 "Expected TS5095 for bundler+system, got: {codes:?}"
2681 );
2682 }
2683
2684 #[test]
2685 fn test_ts5095_not_emitted_for_bundler_with_es2015() {
2686 let source = r#"{"compilerOptions":{"module":"es2015","moduleResolution":"bundler"}}"#;
2687 let parsed = parse_tsconfig_with_diagnostics(source, "tsconfig.json").unwrap();
2688 let codes: Vec<u32> = parsed.diagnostics.iter().map(|d| d.code).collect();
2689 assert!(
2690 !codes.contains(&5095),
2691 "Should NOT emit TS5095 for bundler+es2015, got: {codes:?}"
2692 );
2693 }
2694
2695 #[test]
2696 fn test_ts5095_not_emitted_for_bundler_with_esnext() {
2697 let source = r#"{"compilerOptions":{"module":"esnext","moduleResolution":"bundler"}}"#;
2698 let parsed = parse_tsconfig_with_diagnostics(source, "tsconfig.json").unwrap();
2699 let codes: Vec<u32> = parsed.diagnostics.iter().map(|d| d.code).collect();
2700 assert!(
2701 !codes.contains(&5095),
2702 "Should NOT emit TS5095 for bundler+esnext, got: {codes:?}"
2703 );
2704 }
2705
2706 #[test]
2707 fn test_ts5095_not_emitted_for_bundler_with_preserve() {
2708 let source = r#"{"compilerOptions":{"module":"preserve","moduleResolution":"bundler"}}"#;
2709 let parsed = parse_tsconfig_with_diagnostics(source, "tsconfig.json").unwrap();
2710 let codes: Vec<u32> = parsed.diagnostics.iter().map(|d| d.code).collect();
2711 assert!(
2712 !codes.contains(&5095),
2713 "Should NOT emit TS5095 for bundler+preserve, got: {codes:?}"
2714 );
2715 }
2716
2717 #[test]
2718 fn test_ts5095_not_emitted_for_bundler_with_node16() {
2719 let source = r#"{"compilerOptions":{"module":"node16","moduleResolution":"bundler"}}"#;
2720 let parsed = parse_tsconfig_with_diagnostics(source, "tsconfig.json").unwrap();
2721 let codes: Vec<u32> = parsed.diagnostics.iter().map(|d| d.code).collect();
2722 assert!(
2723 !codes.contains(&5095),
2724 "Should NOT emit TS5095 for bundler+node16, got: {codes:?}"
2725 );
2726 }
2727
2728 #[test]
2729 fn test_ts5095_not_emitted_for_bundler_with_node18() {
2730 let source = r#"{"compilerOptions":{"module":"node18","moduleResolution":"bundler"}}"#;
2731 let parsed = parse_tsconfig_with_diagnostics(source, "tsconfig.json").unwrap();
2732 let codes: Vec<u32> = parsed.diagnostics.iter().map(|d| d.code).collect();
2733 assert!(
2734 !codes.contains(&5095),
2735 "Should NOT emit TS5095 for bundler+node18, got: {codes:?}"
2736 );
2737 }
2738
2739 #[test]
2740 fn test_ts5095_not_emitted_for_bundler_with_nodenext() {
2741 let source = r#"{"compilerOptions":{"module":"nodenext","moduleResolution":"bundler"}}"#;
2742 let parsed = parse_tsconfig_with_diagnostics(source, "tsconfig.json").unwrap();
2743 let codes: Vec<u32> = parsed.diagnostics.iter().map(|d| d.code).collect();
2744 assert!(
2745 !codes.contains(&5095),
2746 "Should NOT emit TS5095 for bundler+nodenext, got: {codes:?}"
2747 );
2748 }
2749
2750 #[test]
2751 fn test_ts5095_not_emitted_for_node16_resolution() {
2752 let source = r#"{"compilerOptions":{"module":"commonjs","moduleResolution":"node16"}}"#;
2753 let parsed = parse_tsconfig_with_diagnostics(source, "tsconfig.json").unwrap();
2754 let codes: Vec<u32> = parsed.diagnostics.iter().map(|d| d.code).collect();
2755 assert!(
2756 !codes.contains(&5095),
2757 "Should NOT emit TS5095 for node16 resolution, got: {codes:?}"
2758 );
2759 }
2760
2761 #[test]
2762 fn test_ts5103_emitted_for_invalid_ignore_deprecations() {
2763 let source = r#"{"compilerOptions":{"ignoreDeprecations":"6.0"}}"#;
2764 let parsed = parse_tsconfig_with_diagnostics(source, "tsconfig.json").unwrap();
2765 let codes: Vec<u32> = parsed.diagnostics.iter().map(|d| d.code).collect();
2766 assert!(
2767 codes.contains(&5103),
2768 "Expected TS5103 for ignoreDeprecations='6.0', got: {codes:?}"
2769 );
2770 }
2771
2772 #[test]
2773 fn test_ts5103_emitted_for_wrong_version() {
2774 let source = r#"{"compilerOptions":{"ignoreDeprecations":"5.1"}}"#;
2775 let parsed = parse_tsconfig_with_diagnostics(source, "tsconfig.json").unwrap();
2776 let codes: Vec<u32> = parsed.diagnostics.iter().map(|d| d.code).collect();
2777 assert!(
2778 codes.contains(&5103),
2779 "Expected TS5103 for ignoreDeprecations='5.1', got: {codes:?}"
2780 );
2781 }
2782
2783 #[test]
2784 fn test_ts5103_not_emitted_for_valid_value() {
2785 let source = r#"{"compilerOptions":{"ignoreDeprecations":"5.0"}}"#;
2786 let parsed = parse_tsconfig_with_diagnostics(source, "tsconfig.json").unwrap();
2787 let codes: Vec<u32> = parsed.diagnostics.iter().map(|d| d.code).collect();
2788 assert!(
2789 !codes.contains(&5103),
2790 "Should NOT emit TS5103 for valid ignoreDeprecations='5.0', got: {codes:?}"
2791 );
2792 }
2793
2794 #[test]
2795 fn test_ts5103_not_emitted_when_absent() {
2796 let source = r#"{"compilerOptions":{"strict":true}}"#;
2797 let parsed = parse_tsconfig_with_diagnostics(source, "tsconfig.json").unwrap();
2798 let codes: Vec<u32> = parsed.diagnostics.iter().map(|d| d.code).collect();
2799 assert!(
2800 !codes.contains(&5103),
2801 "Should NOT emit TS5103 when ignoreDeprecations is absent, got: {codes:?}"
2802 );
2803 }
2804
2805 #[test]
2806 fn test_ts5110_node16_resolution_with_commonjs_module() {
2807 let source = r#"{"compilerOptions":{"module":"commonjs","moduleResolution":"node16"}}"#;
2808 let parsed = parse_tsconfig_with_diagnostics(source, "tsconfig.json").unwrap();
2809 let codes: Vec<u32> = parsed.diagnostics.iter().map(|d| d.code).collect();
2810 assert!(
2811 codes.contains(&5110),
2812 "Should emit TS5110 for node16 resolution with commonjs module, got: {codes:?}"
2813 );
2814 }
2815
2816 #[test]
2817 fn test_ts5110_nodenext_resolution_with_es2022_module() {
2818 let source = r#"{"compilerOptions":{"module":"es2022","moduleResolution":"nodenext"}}"#;
2819 let parsed = parse_tsconfig_with_diagnostics(source, "tsconfig.json").unwrap();
2820 let codes: Vec<u32> = parsed.diagnostics.iter().map(|d| d.code).collect();
2821 assert!(
2822 codes.contains(&5110),
2823 "Should emit TS5110 for nodenext resolution with es2022 module, got: {codes:?}"
2824 );
2825 }
2826
2827 #[test]
2828 fn test_ts5110_not_emitted_for_matching_node16() {
2829 let source = r#"{"compilerOptions":{"module":"node16","moduleResolution":"node16"}}"#;
2830 let parsed = parse_tsconfig_with_diagnostics(source, "tsconfig.json").unwrap();
2831 let codes: Vec<u32> = parsed.diagnostics.iter().map(|d| d.code).collect();
2832 assert!(
2833 !codes.contains(&5110),
2834 "Should NOT emit TS5110 when module matches moduleResolution, got: {codes:?}"
2835 );
2836 }
2837
2838 #[test]
2839 fn test_ts5110_not_emitted_for_matching_nodenext() {
2840 let source = r#"{"compilerOptions":{"module":"nodenext","moduleResolution":"nodenext"}}"#;
2841 let parsed = parse_tsconfig_with_diagnostics(source, "tsconfig.json").unwrap();
2842 let codes: Vec<u32> = parsed.diagnostics.iter().map(|d| d.code).collect();
2843 assert!(
2844 !codes.contains(&5110),
2845 "Should NOT emit TS5110 when module matches moduleResolution, got: {codes:?}"
2846 );
2847 }
2848
2849 #[test]
2850 fn test_ts5069_emit_declaration_only_without_declaration() {
2851 let source = r#"{"compilerOptions":{"emitDeclarationOnly":true}}"#;
2852 let parsed = parse_tsconfig_with_diagnostics(source, "tsconfig.json").unwrap();
2853 let codes: Vec<u32> = parsed.diagnostics.iter().map(|d| d.code).collect();
2854 assert!(
2855 codes.contains(&5069),
2856 "Expected TS5069 for emitDeclarationOnly without declaration, got: {:?}",
2857 codes
2858 );
2859 }
2860
2861 #[test]
2862 fn test_ts5069_not_emitted_with_declaration() {
2863 let source = r#"{"compilerOptions":{"emitDeclarationOnly":true,"declaration":true}}"#;
2864 let parsed = parse_tsconfig_with_diagnostics(source, "tsconfig.json").unwrap();
2865 let codes: Vec<u32> = parsed.diagnostics.iter().map(|d| d.code).collect();
2866 assert!(
2867 !codes.contains(&5069),
2868 "Should NOT emit TS5069 when declaration is true, got: {:?}",
2869 codes
2870 );
2871 }
2872
2873 #[test]
2874 fn test_ts5069_not_emitted_with_composite() {
2875 let source = r#"{"compilerOptions":{"emitDeclarationOnly":true,"composite":true}}"#;
2876 let parsed = parse_tsconfig_with_diagnostics(source, "tsconfig.json").unwrap();
2877 let codes: Vec<u32> = parsed.diagnostics.iter().map(|d| d.code).collect();
2878 assert!(
2879 !codes.contains(&5069),
2880 "Should NOT emit TS5069 when composite is true, got: {:?}",
2881 codes
2882 );
2883 }
2884
2885 #[test]
2886 fn test_ts5069_declaration_map_without_declaration() {
2887 let source = r#"{"compilerOptions":{"declarationMap":true}}"#;
2888 let parsed = parse_tsconfig_with_diagnostics(source, "tsconfig.json").unwrap();
2889 let codes: Vec<u32> = parsed.diagnostics.iter().map(|d| d.code).collect();
2890 assert!(
2891 codes.contains(&5069),
2892 "Expected TS5069 for declarationMap without declaration, got: {:?}",
2893 codes
2894 );
2895 }
2896
2897 #[test]
2898 fn test_ts5053_sourcemap_with_inline_sourcemap() {
2899 let source = r#"{"compilerOptions":{"sourceMap":true,"inlineSourceMap":true}}"#;
2900 let parsed = parse_tsconfig_with_diagnostics(source, "tsconfig.json").unwrap();
2901 let codes: Vec<u32> = parsed.diagnostics.iter().map(|d| d.code).collect();
2902 assert!(
2903 codes.contains(&5053),
2904 "Expected TS5053 for sourceMap with inlineSourceMap, got: {:?}",
2905 codes
2906 );
2907 let count = codes.iter().filter(|&&c| c == 5053).count();
2909 assert_eq!(
2910 count, 2,
2911 "Expected 2 TS5053 diagnostics (one per key), got: {}",
2912 count
2913 );
2914 }
2915
2916 #[test]
2917 fn test_ts5053_not_emitted_without_conflict() {
2918 let source = r#"{"compilerOptions":{"sourceMap":true}}"#;
2919 let parsed = parse_tsconfig_with_diagnostics(source, "tsconfig.json").unwrap();
2920 let codes: Vec<u32> = parsed.diagnostics.iter().map(|d| d.code).collect();
2921 assert!(
2922 !codes.contains(&5053),
2923 "Should NOT emit TS5053 for sourceMap alone, got: {:?}",
2924 codes
2925 );
2926 }
2927
2928 #[test]
2929 fn test_ts5053_allow_js_with_isolated_declarations() {
2930 let source = r#"{"compilerOptions":{"allowJs":true,"isolatedDeclarations":true,"declaration":true}}"#;
2931 let parsed = parse_tsconfig_with_diagnostics(source, "tsconfig.json").unwrap();
2932 let codes: Vec<u32> = parsed.diagnostics.iter().map(|d| d.code).collect();
2933 assert!(
2934 codes.contains(&5053),
2935 "Expected TS5053 for allowJs with isolatedDeclarations, got: {:?}",
2936 codes
2937 );
2938 }
2939
2940 #[test]
2941 fn test_ts5070_resolve_json_module_with_classic_module_resolution() {
2942 let source =
2943 r#"{"compilerOptions":{"resolveJsonModule":true,"moduleResolution":"classic"}}"#;
2944 let parsed = parse_tsconfig_with_diagnostics(source, "tsconfig.json").unwrap();
2945 let codes: Vec<u32> = parsed.diagnostics.iter().map(|d| d.code).collect();
2946 assert!(
2947 codes.contains(&5070),
2948 "Expected TS5070 for resolveJsonModule with classic moduleResolution, got: {:?}",
2949 codes
2950 );
2951 }
2952
2953 #[test]
2954 fn test_ts5070_resolve_json_module_with_amd_module() {
2955 let source = r#"{"compilerOptions":{"resolveJsonModule":true,"module":"amd"}}"#;
2957 let parsed = parse_tsconfig_with_diagnostics(source, "tsconfig.json").unwrap();
2958 let codes: Vec<u32> = parsed.diagnostics.iter().map(|d| d.code).collect();
2959 assert!(
2960 codes.contains(&5070),
2961 "Expected TS5070 for resolveJsonModule with module=amd (implies classic), got: {:?}",
2962 codes
2963 );
2964 }
2965
2966 #[test]
2967 fn test_ts5071_resolve_json_module_with_system_module() {
2968 let source = r#"{"compilerOptions":{"resolveJsonModule":true,"module":"system"}}"#;
2969 let parsed = parse_tsconfig_with_diagnostics(source, "tsconfig.json").unwrap();
2970 let codes: Vec<u32> = parsed.diagnostics.iter().map(|d| d.code).collect();
2971 assert!(
2972 codes.contains(&5071),
2973 "Expected TS5071 for resolveJsonModule with module=system, got: {:?}",
2974 codes
2975 );
2976 }
2977
2978 #[test]
2979 fn test_ts5071_resolve_json_module_with_none_module() {
2980 let source = r#"{"compilerOptions":{"resolveJsonModule":true,"module":"none"}}"#;
2981 let parsed = parse_tsconfig_with_diagnostics(source, "tsconfig.json").unwrap();
2982 let codes: Vec<u32> = parsed.diagnostics.iter().map(|d| d.code).collect();
2983 assert!(
2984 codes.contains(&5071),
2985 "Expected TS5071 for resolveJsonModule with module=none, got: {:?}",
2986 codes
2987 );
2988 }
2989
2990 #[test]
2991 fn test_ts5098_resolve_package_json_with_classic() {
2992 let source = r#"{"compilerOptions":{"resolvePackageJsonExports":true,"moduleResolution":"classic"}}"#;
2993 let parsed = parse_tsconfig_with_diagnostics(source, "tsconfig.json").unwrap();
2994 let codes: Vec<u32> = parsed.diagnostics.iter().map(|d| d.code).collect();
2995 assert!(
2996 codes.contains(&5098),
2997 "Expected TS5098 for resolvePackageJsonExports with classic moduleResolution, got: {:?}",
2998 codes
2999 );
3000 }
3001
3002 #[test]
3003 fn test_ts5098_not_emitted_with_bundler() {
3004 let source = r#"{"compilerOptions":{"resolvePackageJsonExports":true,"moduleResolution":"bundler"}}"#;
3005 let parsed = parse_tsconfig_with_diagnostics(source, "tsconfig.json").unwrap();
3006 let codes: Vec<u32> = parsed.diagnostics.iter().map(|d| d.code).collect();
3007 assert!(
3008 !codes.contains(&5098),
3009 "Should NOT emit TS5098 with bundler moduleResolution, got: {:?}",
3010 codes
3011 );
3012 }
3013
3014 #[test]
3015 fn test_ts6082_outfile_with_commonjs() {
3016 let source = r#"{"compilerOptions":{"module":"commonjs","outFile":"all.js"}}"#;
3017 let parsed = parse_tsconfig_with_diagnostics(source, "tsconfig.json").unwrap();
3018 let codes: Vec<u32> = parsed.diagnostics.iter().map(|d| d.code).collect();
3019 assert!(
3020 codes.contains(&6082),
3021 "Expected TS6082 for outFile+commonjs, got: {codes:?}"
3022 );
3023 let count = codes.iter().filter(|&&c| c == 6082).count();
3025 assert_eq!(
3026 count, 2,
3027 "Expected two TS6082 diagnostics (module + outFile keys), got {count}"
3028 );
3029 }
3030
3031 #[test]
3032 fn test_ts6082_outfile_with_umd() {
3033 let source = r#"{"compilerOptions":{"module":"umd","outFile":"all.js"}}"#;
3034 let parsed = parse_tsconfig_with_diagnostics(source, "tsconfig.json").unwrap();
3035 let codes: Vec<u32> = parsed.diagnostics.iter().map(|d| d.code).collect();
3036 assert!(
3037 codes.contains(&6082),
3038 "Expected TS6082 for outFile+umd, got: {codes:?}"
3039 );
3040 }
3041
3042 #[test]
3043 fn test_ts6082_outfile_with_es6() {
3044 let source = r#"{"compilerOptions":{"module":"es6","outFile":"all.js"}}"#;
3045 let parsed = parse_tsconfig_with_diagnostics(source, "tsconfig.json").unwrap();
3046 let codes: Vec<u32> = parsed.diagnostics.iter().map(|d| d.code).collect();
3047 assert!(
3048 codes.contains(&6082),
3049 "Expected TS6082 for outFile+es6, got: {codes:?}"
3050 );
3051 }
3052
3053 #[test]
3054 fn test_ts6082_not_emitted_for_amd() {
3055 let source = r#"{"compilerOptions":{"module":"amd","outFile":"all.js"}}"#;
3056 let parsed = parse_tsconfig_with_diagnostics(source, "tsconfig.json").unwrap();
3057 let codes: Vec<u32> = parsed.diagnostics.iter().map(|d| d.code).collect();
3058 assert!(
3059 !codes.contains(&6082),
3060 "Should NOT emit TS6082 for outFile+amd, got: {codes:?}"
3061 );
3062 }
3063
3064 #[test]
3065 fn test_ts6082_not_emitted_for_system() {
3066 let source = r#"{"compilerOptions":{"module":"system","outFile":"all.js"}}"#;
3067 let parsed = parse_tsconfig_with_diagnostics(source, "tsconfig.json").unwrap();
3068 let codes: Vec<u32> = parsed.diagnostics.iter().map(|d| d.code).collect();
3069 assert!(
3070 !codes.contains(&6082),
3071 "Should NOT emit TS6082 for outFile+system, got: {codes:?}"
3072 );
3073 }
3074
3075 #[test]
3076 fn test_ts6082_not_emitted_with_emit_declaration_only() {
3077 let source = r#"{"compilerOptions":{"module":"commonjs","outFile":"all.js","emitDeclarationOnly":true,"declaration":true}}"#;
3078 let parsed = parse_tsconfig_with_diagnostics(source, "tsconfig.json").unwrap();
3079 let codes: Vec<u32> = parsed.diagnostics.iter().map(|d| d.code).collect();
3080 assert!(
3081 !codes.contains(&6082),
3082 "Should NOT emit TS6082 when emitDeclarationOnly is true, got: {codes:?}"
3083 );
3084 }
3085
3086 #[test]
3087 fn test_ts6082_not_emitted_without_outfile() {
3088 let source = r#"{"compilerOptions":{"module":"commonjs"}}"#;
3089 let parsed = parse_tsconfig_with_diagnostics(source, "tsconfig.json").unwrap();
3090 let codes: Vec<u32> = parsed.diagnostics.iter().map(|d| d.code).collect();
3091 assert!(
3092 !codes.contains(&6082),
3093 "Should NOT emit TS6082 without outFile, got: {codes:?}"
3094 );
3095 }
3096}