1use once_cell::sync::Lazy;
2use rustc_hash::FxHashMap;
3use std::sync::Mutex;
4use wasm_bindgen::prelude::{JsValue, wasm_bindgen};
5
6type LibFileCache = FxHashMap<(String, u64), Arc<lib_loader::LibFile>>;
7
8static LIB_FILE_CACHE: Lazy<Mutex<LibFileCache>> = Lazy::new(|| Mutex::new(FxHashMap::default()));
11
12fn hash_lib_content(content: &str) -> u64 {
14 use std::collections::hash_map::DefaultHasher;
15 use std::hash::{Hash, Hasher};
16 let mut hasher = DefaultHasher::new();
17 content.hash(&mut hasher);
18 hasher.finish()
19}
20
21fn get_or_create_lib_file(file_name: String, source_text: String) -> Arc<lib_loader::LibFile> {
23 let content_hash = hash_lib_content(&source_text);
24 let cache_key = (file_name.clone(), content_hash);
25
26 {
28 let cache = LIB_FILE_CACHE.lock().unwrap();
29 if let Some(cached) = cache.get(&cache_key) {
30 return Arc::clone(cached);
31 }
32 }
33
34 let mut lib_parser = ParserState::new(file_name.clone(), source_text);
36 let source_file_idx = lib_parser.parse_source_file();
37
38 let mut lib_binder = BinderState::new();
39 lib_binder.bind_source_file(lib_parser.get_arena(), source_file_idx);
40
41 let arena = Arc::new(lib_parser.into_arena());
42 let binder = Arc::new(lib_binder);
43
44 let lib_file = Arc::new(lib_loader::LibFile::new(file_name, arena, binder));
45
46 {
48 let mut cache = LIB_FILE_CACHE.lock().unwrap();
49 cache.insert(cache_key, Arc::clone(&lib_file));
50 }
51
52 lib_file
53}
54
55#[cfg(test)]
57#[path = "../tests/test_fixtures.rs"]
58pub mod test_fixtures;
59
60pub use tsz_common::interner;
62pub use tsz_common::interner::{Atom, Interner, ShardedInterner};
63#[cfg(test)]
64#[path = "../tests/interner_tests.rs"]
65mod interner_tests;
66
67pub use tsz_common::common;
68pub use tsz_common::common::{ModuleKind, NewLineKind, ScriptTarget};
69
70pub use tsz_common::limits;
71
72pub use tsz_scanner as scanner;
74pub use tsz_scanner::char_codes;
75pub use tsz_scanner::scanner_impl;
76pub use tsz_scanner::scanner_impl::{ScannerState, TokenFlags};
77pub use tsz_scanner::*;
78#[cfg(test)]
79#[path = "../tests/scanner_impl_tests.rs"]
80mod scanner_impl_tests;
81#[cfg(test)]
82#[path = "../tests/scanner_tests.rs"]
83mod scanner_tests;
84
85pub use tsz_parser::parser;
87
88pub use tsz_parser::syntax;
90
91#[cfg(test)]
93#[path = "../tests/parser_state_tests.rs"]
94mod parser_state_tests;
95
96#[cfg(test)]
98#[path = "../tests/parser_ts1038_tests.rs"]
99mod parser_ts1038_tests;
100
101#[cfg(test)]
103#[path = "../tests/control_flow_validation_tests.rs"]
104mod control_flow_validation_tests;
105
106#[cfg(test)]
108#[path = "../tests/regex_flag_tests.rs"]
109mod regex_flag_tests;
110
111pub use tsz_binder as binder;
113
114#[cfg(test)]
116#[path = "../tests/binder_state_tests.rs"]
117mod binder_state_tests;
118
119pub use tsz_binder::module_resolution_debug;
121
122pub use tsz_binder::lib_loader;
124
125pub use tsz_checker as checker;
127
128#[cfg(test)]
129#[path = "../tests/checker_state_tests.rs"]
130mod checker_state_tests;
131
132#[cfg(test)]
133#[path = "../tests/variable_redeclaration_tests.rs"]
134mod variable_redeclaration_tests;
135
136#[cfg(test)]
137#[path = "../tests/strict_mode_and_module_tests.rs"]
138mod strict_mode_and_module_tests;
139
140#[cfg(test)]
141#[path = "../tests/overload_compatibility_tests.rs"]
142mod overload_compatibility_tests;
143
144#[cfg(test)]
146#[path = "../tests/module_resolution_tests.rs"]
147mod module_resolution_tests;
148
149pub use checker::state::{CheckerState, MAX_CALL_DEPTH, MAX_INSTANTIATION_DEPTH};
150
151pub use tsz_emitter::emitter;
153#[cfg(test)]
154#[path = "../tests/transform_api_tests.rs"]
155mod transform_api_tests;
156
157pub use tsz_emitter::output::printer;
159
160pub use tsz_emitter::safe_slice;
162#[cfg(test)]
163#[path = "../tests/printer_tests.rs"]
164mod printer_tests;
165
166pub use tsz_common::span;
168
169pub mod source_file;
171
172pub mod diagnostics;
174
175pub use tsz_emitter::enums;
177
178pub mod parallel;
180
181pub use tsz_common::comments;
183#[cfg(test)]
184#[path = "../tests/comments_tests.rs"]
185mod comments_tests;
186
187pub use tsz_common::source_map;
189#[cfg(test)]
190#[path = "../tests/source_map_test_utils.rs"]
191mod source_map_test_utils;
192#[cfg(test)]
193#[path = "../tests/source_map_tests_1.rs"]
194mod source_map_tests_1;
195#[cfg(test)]
196#[path = "../tests/source_map_tests_2.rs"]
197mod source_map_tests_2;
198#[cfg(test)]
199#[path = "../tests/source_map_tests_3.rs"]
200mod source_map_tests_3;
201#[cfg(test)]
202#[path = "../tests/source_map_tests_4.rs"]
203mod source_map_tests_4;
204
205pub use tsz_emitter::output::source_writer;
207#[cfg(test)]
208#[path = "../tests/source_writer_tests.rs"]
209mod source_writer_tests;
210
211pub use tsz_emitter::context;
213
214pub use tsz_emitter::lowering;
216
217pub use tsz_emitter::declaration_emitter;
219
220pub use tsz_emitter::transforms;
222
223pub use tsz_solver;
225
226pub use tsz_lsp as lsp;
228
229#[cfg(test)]
231#[path = "../tests/test_harness.rs"]
232mod test_harness;
233
234#[cfg(test)]
236#[path = "../tests/isolated_test_runner.rs"]
237mod isolated_test_runner;
238
239pub mod config;
241
242#[cfg(not(target_arch = "wasm32"))]
250pub mod module_resolver;
251#[cfg(not(target_arch = "wasm32"))]
252mod module_resolver_helpers;
253#[cfg(not(target_arch = "wasm32"))]
254pub use module_resolver::{ModuleExtension, ModuleResolver, ResolutionFailure, ResolvedModule};
255
256pub mod imports;
258pub use imports::{ImportDeclaration, ImportKind, ImportTracker, ImportedBinding};
259
260pub mod exports;
261pub use exports::{ExportDeclaration, ExportKind, ExportTracker, ExportedBinding};
262
263#[cfg(not(target_arch = "wasm32"))]
265pub mod module_graph;
266#[cfg(not(target_arch = "wasm32"))]
267pub use module_graph::{CircularDependency, ModuleGraph, ModuleId, ModuleInfo};
268
269#[wasm_bindgen(js_name = createScanner)]
276pub fn create_scanner(text: String, skip_trivia: bool) -> ScannerState {
277 ScannerState::new(text, skip_trivia)
278}
279
280use crate::binder::BinderState;
285use crate::checker::context::LibContext;
286use crate::context::emit::EmitContext;
287use crate::context::transform::TransformContext;
288use crate::emitter::{Printer, PrinterOptions};
289use crate::lib_loader::LibFile;
290use crate::lowering::LoweringPass;
291use crate::lsp::diagnostics::convert_diagnostic;
292use crate::lsp::position::{LineMap, Position, Range};
293use crate::lsp::resolver::ScopeCache;
294use crate::lsp::{
295 CodeActionContext, CodeActionKind, CodeActionProvider, Completions, DocumentSymbolProvider,
296 FindReferences, GoToDefinition, HoverProvider, ImportCandidate, ImportCandidateKind,
297 RenameProvider, SemanticTokensProvider, SignatureHelpProvider,
298};
299use crate::parser::ParserState;
300use serde::Deserialize;
301use std::sync::Arc;
302use tsz_solver::TypeInterner;
303
304#[derive(Deserialize)]
305#[serde(rename_all = "camelCase")]
306struct ImportCandidateInput {
307 module_specifier: String,
308 local_name: String,
309 kind: String,
310 export_name: Option<String>,
311 #[serde(default)]
312 is_type_only: bool,
313}
314
315#[derive(Deserialize)]
316#[serde(rename_all = "camelCase")]
317struct CodeActionContextInput {
318 #[serde(default)]
319 diagnostics: Vec<tsz_lsp::diagnostics::LspDiagnostic>,
320 #[serde(default)]
321 only: Option<Vec<CodeActionKind>>,
322 #[serde(default)]
323 import_candidates: Vec<ImportCandidateInput>,
324}
325
326#[derive(Deserialize, Clone, Debug, Default)]
329#[serde(rename_all = "camelCase")]
330struct CompilerOptions {
331 #[serde(default, deserialize_with = "deserialize_bool_option")]
333 strict: Option<bool>,
334
335 #[serde(default, deserialize_with = "deserialize_bool_option")]
337 no_implicit_any: Option<bool>,
338
339 #[serde(default, deserialize_with = "deserialize_bool_option")]
341 strict_null_checks: Option<bool>,
342
343 #[serde(default, deserialize_with = "deserialize_bool_option")]
345 strict_function_types: Option<bool>,
346
347 #[serde(default, deserialize_with = "deserialize_bool_option")]
349 strict_property_initialization: Option<bool>,
350
351 #[serde(default, deserialize_with = "deserialize_bool_option")]
353 no_implicit_returns: Option<bool>,
354
355 #[serde(default, deserialize_with = "deserialize_bool_option")]
357 no_implicit_this: Option<bool>,
358
359 #[serde(default, deserialize_with = "deserialize_target_or_module")]
361 target: Option<u32>,
362
363 #[serde(default, deserialize_with = "deserialize_bool_option")]
365 no_lib: Option<bool>,
366}
367
368fn deserialize_bool_option<'de, D>(deserializer: D) -> Result<Option<bool>, D::Error>
371where
372 D: serde::Deserializer<'de>,
373{
374 use serde::de::{self, Visitor};
375
376 struct BoolOptionVisitor;
377
378 impl<'de> Visitor<'de> for BoolOptionVisitor {
379 type Value = Option<bool>;
380
381 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
382 formatter.write_str("a boolean, string, or comma-separated list of booleans")
383 }
384
385 fn visit_none<E>(self) -> Result<Self::Value, E>
386 where
387 E: de::Error,
388 {
389 Ok(None)
390 }
391
392 fn visit_unit<E>(self) -> Result<Self::Value, E>
393 where
394 E: de::Error,
395 {
396 Ok(None)
397 }
398
399 fn visit_bool<E>(self, value: bool) -> Result<Self::Value, E>
400 where
401 E: de::Error,
402 {
403 Ok(Some(value))
404 }
405
406 fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
407 where
408 E: de::Error,
409 {
410 let first_value = value.split(',').next().unwrap_or(value).trim();
412 let result = match first_value.to_lowercase().as_str() {
413 "true" | "1" => Some(true),
414 "false" | "0" => Some(false),
415 _ => None,
416 };
417 Ok(result)
418 }
419 }
420
421 deserializer.deserialize_any(BoolOptionVisitor)
422}
423
424fn deserialize_target_or_module<'de, D>(deserializer: D) -> Result<Option<u32>, D::Error>
427where
428 D: serde::Deserializer<'de>,
429{
430 use serde::de::{self, Visitor};
431
432 struct TargetOrModuleVisitor;
433
434 impl<'de> Visitor<'de> for TargetOrModuleVisitor {
435 type Value = Option<u32>;
436
437 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
438 formatter.write_str("a string or integer representing target/module")
439 }
440
441 fn visit_none<E>(self) -> Result<Self::Value, E>
442 where
443 E: de::Error,
444 {
445 Ok(None)
446 }
447
448 fn visit_unit<E>(self) -> Result<Self::Value, E>
449 where
450 E: de::Error,
451 {
452 Ok(None)
453 }
454
455 fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E>
456 where
457 E: de::Error,
458 {
459 Ok(Some(value as u32))
460 }
461
462 fn visit_i64<E>(self, value: i64) -> Result<Self::Value, E>
463 where
464 E: de::Error,
465 {
466 Ok(Some(value as u32))
467 }
468
469 fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
470 where
471 E: de::Error,
472 {
473 let result = match value.to_uppercase().as_str() {
477 "ES3" | "NONE" => 0,
480 "ES5" | "COMMONJS" => 1,
481 "ES2015" | "ES6" | "AMD" => 2,
482 "ES2016" | "UMD" => 3,
483 "ES2017" | "SYSTEM" => 4,
484 "ES2018" => 5,
485 "ES2019" => 6,
486 "ES2020" => 7,
487 "ES2021" => 8,
488 "ES2022" => 9,
489 "ES2023" => 10,
490 "ESNEXT" => 99,
491 "NODE16" => 100,
492 "NODENEXT" => 199,
493 _ => return Ok(None), };
495 Ok(Some(result))
496 }
497 }
498
499 deserializer.deserialize_any(TargetOrModuleVisitor)
500}
501
502impl CompilerOptions {
503 fn resolve_bool(&self, specific: Option<bool>, strict_implies: bool) -> bool {
506 if let Some(value) = specific {
507 return value;
508 }
509 if strict_implies {
510 return self.strict.unwrap_or(false);
511 }
512 false
513 }
514
515 pub fn get_no_implicit_any(&self) -> bool {
517 self.no_implicit_any.unwrap_or(self.strict.unwrap_or(true))
518 }
519
520 pub fn get_strict_null_checks(&self) -> bool {
522 self.resolve_bool(self.strict_null_checks, true)
523 }
524
525 pub fn get_strict_function_types(&self) -> bool {
527 self.resolve_bool(self.strict_function_types, true)
528 }
529
530 pub fn get_strict_property_initialization(&self) -> bool {
532 self.resolve_bool(self.strict_property_initialization, true)
533 }
534
535 pub fn get_no_implicit_returns(&self) -> bool {
537 self.resolve_bool(self.no_implicit_returns, false)
538 }
539
540 pub fn get_no_implicit_this(&self) -> bool {
542 self.resolve_bool(self.no_implicit_this, true)
543 }
544
545 fn resolve_target(&self) -> crate::checker::context::ScriptTarget {
546 use crate::checker::context::ScriptTarget;
547
548 match self.target {
549 Some(0) => ScriptTarget::ES3,
550 Some(1) => ScriptTarget::ES5,
551 Some(2) => ScriptTarget::ES2015,
552 Some(3) => ScriptTarget::ES2016,
553 Some(4) => ScriptTarget::ES2017,
554 Some(5) => ScriptTarget::ES2018,
555 Some(6) => ScriptTarget::ES2019,
556 Some(7) => ScriptTarget::ES2020,
557 Some(_) => ScriptTarget::ESNext,
558 None => ScriptTarget::default(),
559 }
560 }
561
562 pub fn to_checker_options(&self) -> crate::checker::context::CheckerOptions {
564 let strict = self.strict.unwrap_or(false);
565 let strict_null_checks = self.get_strict_null_checks();
566 crate::checker::context::CheckerOptions {
567 strict,
568 no_implicit_any: self.get_no_implicit_any(),
569 no_implicit_returns: self.get_no_implicit_returns(),
570 strict_null_checks,
571 strict_function_types: self.get_strict_function_types(),
572 strict_property_initialization: self.get_strict_property_initialization(),
573 no_implicit_this: self.get_no_implicit_this(),
574 use_unknown_in_catch_variables: strict_null_checks,
575 isolated_modules: false,
576 no_unchecked_indexed_access: false,
577 strict_bind_call_apply: false,
578 exact_optional_property_types: false,
579 no_lib: self.no_lib.unwrap_or(false),
580 no_types_and_symbols: false,
581 target: self.resolve_target(),
582 module: crate::common::ModuleKind::None,
583 jsx_factory: "React.createElement".to_string(),
584 jsx_fragment_factory: "React.Fragment".to_string(),
585
586 es_module_interop: false,
587 allow_synthetic_default_imports: false,
588 allow_unreachable_code: None,
589 no_property_access_from_index_signature: false,
590 sound_mode: false,
591 experimental_decorators: false,
592 no_unused_locals: false,
593 no_unused_parameters: false,
594 always_strict: strict,
595 resolve_json_module: false, check_js: false, no_resolve: false,
598 no_unchecked_side_effect_imports: false,
599 no_implicit_override: false,
600 jsx_mode: tsz_common::checker_options::JsxMode::None,
601 module_explicitly_set: false,
602 suppress_excess_property_errors: false,
603 suppress_implicit_any_index_errors: false,
604 }
605 }
606}
607
608impl TryFrom<ImportCandidateInput> for ImportCandidate {
609 type Error = JsValue;
610
611 fn try_from(input: ImportCandidateInput) -> Result<Self, Self::Error> {
612 let local_name = input.local_name;
613 let kind = match input.kind.as_str() {
614 "named" => {
615 let export_name = input.export_name.unwrap_or_else(|| local_name.clone());
616 ImportCandidateKind::Named { export_name }
617 }
618 "default" => ImportCandidateKind::Default,
619 "namespace" => ImportCandidateKind::Namespace,
620 other => {
621 return Err(JsValue::from_str(&format!(
622 "Unsupported import candidate kind: {other}"
623 )));
624 }
625 };
626
627 Ok(Self {
628 module_specifier: input.module_specifier,
629 local_name,
630 kind,
631 is_type_only: input.is_type_only,
632 })
633 }
634}
635
636#[wasm_bindgen]
638pub struct WasmTransformContext {
639 inner: TransformContext,
640 target_es5: bool,
641 module_kind: ModuleKind,
642}
643
644#[wasm_bindgen]
645impl WasmTransformContext {
646 #[wasm_bindgen(js_name = getCount)]
648 pub fn get_count(&self) -> usize {
649 self.inner.len()
650 }
651}
652
653#[wasm_bindgen]
656pub struct Parser {
657 parser: ParserState,
658 source_file_idx: Option<parser::NodeIndex>,
659 binder: Option<BinderState>,
660 type_interner: TypeInterner,
663 line_map: Option<LineMap>,
665 type_cache: Option<checker::TypeCache>,
668 scope_cache: ScopeCache,
671 lib_files: Vec<Arc<LibFile>>,
673 compiler_options: CompilerOptions,
675}
676
677#[wasm_bindgen]
678impl Parser {
679 #[wasm_bindgen(constructor)]
681 pub fn new(file_name: String, source_text: String) -> Self {
682 Self {
683 parser: ParserState::new(file_name, source_text),
684 source_file_idx: None,
685 binder: None,
686 type_interner: TypeInterner::new(),
687 line_map: None,
688 type_cache: None,
689 scope_cache: ScopeCache::default(),
690 lib_files: Vec::new(),
691 compiler_options: CompilerOptions::default(),
692 }
693 }
694
695 #[wasm_bindgen(js_name = setCompilerOptions)]
710 pub fn set_compiler_options(&mut self, options_json: &str) -> Result<(), JsValue> {
711 match serde_json::from_str::<CompilerOptions>(options_json) {
712 Ok(options) => {
713 self.compiler_options = options;
714 self.type_cache = None;
716 Ok(())
717 }
718 Err(e) => Err(JsValue::from_str(&format!(
719 "Failed to parse compiler options: {e}"
720 ))),
721 }
722 }
723
724 #[wasm_bindgen(js_name = addLibFile)]
728 pub fn add_lib_file(&mut self, file_name: String, source_text: String) {
729 let mut lib_parser = ParserState::new(file_name.clone(), source_text);
730 let source_file_idx = lib_parser.parse_source_file();
731
732 let mut lib_binder = BinderState::new();
733 lib_binder.bind_source_file(lib_parser.get_arena(), source_file_idx);
734
735 let arena = Arc::new(lib_parser.into_arena());
737 let binder = Arc::new(lib_binder);
738
739 let lib_file = Arc::new(LibFile::new(file_name, arena, binder));
741
742 self.lib_files.push(lib_file);
743
744 self.binder = None;
746 self.type_cache = None;
747 }
748
749 #[wasm_bindgen(js_name = parseSourceFile)]
751 pub fn parse_source_file(&mut self) -> u32 {
752 let idx = self.parser.parse_source_file();
753 self.source_file_idx = Some(idx);
754 self.line_map = None;
756 self.binder = None;
757 self.type_cache = None; self.scope_cache.clear();
759 idx.0
760 }
761
762 #[allow(clippy::missing_const_for_fn)] #[wasm_bindgen(js_name = getNodeCount)]
765 pub fn get_node_count(&self) -> usize {
766 self.parser.get_node_count()
767 }
768
769 #[wasm_bindgen(js_name = getDiagnosticsJson)]
771 pub fn get_diagnostics_json(&self) -> String {
772 let diags: Vec<_> = self
773 .parser
774 .get_diagnostics()
775 .iter()
776 .map(|d| {
777 serde_json::json!({
778 "message": d.message,
779 "start": d.start,
780 "length": d.length,
781 "code": d.code,
782 })
783 })
784 .collect();
785 serde_json::to_string(&diags).unwrap_or_else(|_| "[]".to_string())
786 }
787
788 #[wasm_bindgen(js_name = bindSourceFile)]
790 pub fn bind_source_file(&mut self) -> String {
791 if let Some(root_idx) = self.source_file_idx {
792 let mut binder = BinderState::new();
793 binder.bind_source_file_with_libs(self.parser.get_arena(), root_idx, &self.lib_files);
796
797 let symbols: FxHashMap<String, u32> = binder
799 .file_locals
800 .iter()
801 .map(|(name, id)| (name.clone(), id.0))
802 .collect();
803
804 let result = serde_json::json!({
805 "symbols": symbols,
806 "symbolCount": binder.symbols.len(),
807 });
808
809 self.binder = Some(binder);
810 self.scope_cache.clear();
811 serde_json::to_string(&result).unwrap_or_else(|_| "{}".to_string())
812 } else {
813 r#"{"error": "Source file not parsed"}"#.to_string()
814 }
815 }
816
817 #[wasm_bindgen(js_name = checkSourceFile)]
819 pub fn check_source_file(&mut self) -> String {
820 if self.binder.is_none() {
821 if self.source_file_idx.is_some() {
823 self.bind_source_file();
824 }
825 }
826
827 if let (Some(root_idx), Some(binder)) = (self.source_file_idx, &self.binder) {
828 let file_name = self.parser.get_file_name().to_string();
829
830 let checker_options = self.compiler_options.to_checker_options();
832 let mut checker = if let Some(cache) = self.type_cache.take() {
833 CheckerState::with_cache_and_options(
834 self.parser.get_arena(),
835 binder,
836 &self.type_interner,
837 file_name,
838 cache,
839 &checker_options,
840 )
841 } else {
842 CheckerState::with_options(
843 self.parser.get_arena(),
844 binder,
845 &self.type_interner,
846 file_name,
847 &checker_options,
848 )
849 };
850
851 if !self.lib_files.is_empty() {
853 let lib_contexts: Vec<LibContext> = self
854 .lib_files
855 .iter()
856 .map(|lib| LibContext {
857 arena: Arc::clone(&lib.arena),
858 binder: Arc::clone(&lib.binder),
859 })
860 .collect();
861 checker.ctx.set_lib_contexts(lib_contexts);
862 }
863
864 checker.check_source_file(root_idx);
866
867 let diagnostics = checker
868 .ctx
869 .diagnostics
870 .iter()
871 .map(|d| {
872 serde_json::json!({
873 "message_text": d.message_text.clone(),
874 "code": d.code,
875 "start": d.start,
876 "length": d.length,
877 "category": format!("{:?}", d.category),
878 })
879 })
880 .collect::<Vec<_>>();
881
882 self.type_cache = Some(checker.extract_cache());
883
884 let result = serde_json::json!({
885 "typeCount": self.type_interner.len(),
886 "diagnostics": diagnostics,
887 });
888
889 serde_json::to_string(&result).unwrap_or_else(|_| "{}".to_string())
890 } else {
891 r#"{"error": "Source file not parsed or bound"}"#.to_string()
892 }
893 }
894
895 #[wasm_bindgen(js_name = getTypeOfNode)]
897 pub fn get_type_of_node(&mut self, node_idx: u32) -> String {
898 if let (Some(_), Some(binder)) = (self.source_file_idx, &self.binder) {
899 let file_name = self.parser.get_file_name().to_string();
900
901 let checker_options = self.compiler_options.to_checker_options();
903 let mut checker = if let Some(cache) = self.type_cache.take() {
904 CheckerState::with_cache_and_options(
905 self.parser.get_arena(),
906 binder,
907 &self.type_interner,
908 file_name,
909 cache,
910 &checker_options,
911 )
912 } else {
913 CheckerState::with_options(
914 self.parser.get_arena(),
915 binder,
916 &self.type_interner,
917 file_name,
918 &checker_options,
919 )
920 };
921
922 let type_id = checker.get_type_of_node(parser::NodeIndex(node_idx));
923 let result = checker.format_type(type_id);
925 self.type_cache = Some(checker.extract_cache());
926 result
927 } else {
928 "unknown".to_string()
929 }
930 }
931
932 #[wasm_bindgen(js_name = emit)]
934 pub fn emit(&self) -> String {
935 if let Some(root_idx) = self.source_file_idx {
936 let options = PrinterOptions {
937 target: ScriptTarget::ES5,
938 ..Default::default()
939 };
940
941 let mut ctx = EmitContext::with_options(options);
942 ctx.auto_detect_module = true;
943
944 self.emit_with_context(root_idx, ctx)
945 } else {
946 String::new()
947 }
948 }
949
950 #[wasm_bindgen(js_name = emitModern)]
952 pub fn emit_modern(&self) -> String {
953 if let Some(root_idx) = self.source_file_idx {
954 let options = PrinterOptions {
955 target: ScriptTarget::ES2015,
956 ..Default::default()
957 };
958
959 let ctx = EmitContext::with_options(options);
960
961 self.emit_with_context(root_idx, ctx)
962 } else {
963 String::new()
964 }
965 }
966
967 fn emit_with_context(&self, root_idx: parser::NodeIndex, ctx: EmitContext) -> String {
968 let transforms = LoweringPass::new(self.parser.get_arena(), &ctx).run(root_idx);
969
970 let mut printer = Printer::with_transforms_and_options(
971 self.parser.get_arena(),
972 transforms,
973 ctx.options.clone(),
974 );
975 printer.set_target_es5(ctx.target_es5);
976 printer.set_auto_detect_module(ctx.auto_detect_module);
977 printer.set_source_text(self.parser.get_source_text());
978 printer.emit(root_idx);
979 printer.get_output().to_string()
980 }
981
982 #[wasm_bindgen(js_name = generateTransforms)]
984 pub fn generate_transforms(&self, target: u32, module: u32) -> WasmTransformContext {
985 let options = PrinterOptions {
986 target: match target {
987 0 => ScriptTarget::ES3,
988 1 => ScriptTarget::ES5,
989 2 => ScriptTarget::ES2015,
990 3 => ScriptTarget::ES2016,
991 4 => ScriptTarget::ES2017,
992 5 => ScriptTarget::ES2018,
993 6 => ScriptTarget::ES2019,
994 7 => ScriptTarget::ES2020,
995 8 => ScriptTarget::ES2021,
996 9 => ScriptTarget::ES2022,
997 _ => ScriptTarget::ESNext,
998 },
999 module: match module {
1000 1 => ModuleKind::CommonJS,
1001 2 => ModuleKind::AMD,
1002 3 => ModuleKind::UMD,
1003 4 => ModuleKind::System,
1004 5 => ModuleKind::ES2015,
1005 6 => ModuleKind::ES2020,
1006 7 => ModuleKind::ES2022,
1007 99 => ModuleKind::ESNext,
1008 100 => ModuleKind::Node16,
1009 199 => ModuleKind::NodeNext,
1010 _ => ModuleKind::None,
1011 },
1012 ..Default::default()
1013 };
1014
1015 let ctx = EmitContext::with_options(options);
1016 let transforms = if let Some(root_idx) = self.source_file_idx {
1017 let lowering = LoweringPass::new(self.parser.get_arena(), &ctx);
1018 lowering.run(root_idx)
1019 } else {
1020 TransformContext::new()
1021 };
1022
1023 WasmTransformContext {
1024 inner: transforms,
1025 target_es5: ctx.target_es5,
1026 module_kind: ctx.options.module,
1027 }
1028 }
1029
1030 #[wasm_bindgen(js_name = emitWithTransforms)]
1032 pub fn emit_with_transforms(&self, context: &WasmTransformContext) -> String {
1033 if let Some(root_idx) = self.source_file_idx {
1034 let mut printer =
1035 Printer::with_transforms(self.parser.get_arena(), context.inner.clone());
1036 printer.set_target_es5(context.target_es5);
1037 printer.set_module_kind(context.module_kind);
1038 printer.set_source_text(self.parser.get_source_text());
1039 printer.emit(root_idx);
1040 printer.get_output().to_string()
1041 } else {
1042 String::new()
1043 }
1044 }
1045
1046 #[wasm_bindgen(js_name = getAstJson)]
1048 pub fn get_ast_json(&self) -> String {
1049 if let Some(root_idx) = self.source_file_idx {
1050 let arena = self.parser.get_arena();
1051 format!(
1052 "{{\"nodeCount\": {}, \"rootIdx\": {}}}",
1053 arena.len(),
1054 root_idx.0
1055 )
1056 } else {
1057 "{}".to_string()
1058 }
1059 }
1060
1061 #[wasm_bindgen(js_name = debugTypeLowering)]
1063 pub fn debug_type_lowering(&self, interface_name: &str) -> String {
1064 use parser::syntax_kind_ext;
1065 use tsz_lowering::TypeLowering;
1066 use tsz_solver::TypeData;
1067
1068 let arena = self.parser.get_arena();
1069 let mut result = Vec::new();
1070
1071 let mut interface_decls = Vec::new();
1073 for i in 0..arena.len() {
1074 let idx = parser::NodeIndex(i as u32);
1075 if let Some(node) = arena.get(idx)
1076 && node.kind == syntax_kind_ext::INTERFACE_DECLARATION
1077 && let Some(interface) = arena.get_interface(node)
1078 && let Some(name_node) = arena.get(interface.name)
1079 && let Some(ident) = arena.get_identifier(name_node)
1080 && ident.escaped_text == interface_name
1081 {
1082 interface_decls.push(idx);
1083 }
1084 }
1085
1086 if interface_decls.is_empty() {
1087 return format!("Interface '{interface_name}' not found");
1088 }
1089
1090 result.push(format!(
1091 "Found {} declaration(s) for '{}'",
1092 interface_decls.len(),
1093 interface_name
1094 ));
1095
1096 let lowering = TypeLowering::new(arena, &self.type_interner);
1098 let type_id = lowering.lower_interface_declarations(&interface_decls);
1099
1100 result.push(format!("Lowered type ID: {type_id:?}"));
1101
1102 if let Some(key) = self.type_interner.lookup(type_id) {
1104 result.push(format!("Type key: {key:?}"));
1105 if let TypeData::Object(shape_id) = key {
1106 let shape = self.type_interner.object_shape(shape_id);
1107 result.push(format!(
1108 "Object shape properties: {}",
1109 shape.properties.len()
1110 ));
1111 for prop in &shape.properties {
1112 let name = self.type_interner.resolve_atom(prop.name);
1113 result.push(format!(
1114 " Property '{}': type_id={:?}, optional={}",
1115 name, prop.type_id, prop.optional
1116 ));
1117 if let Some(prop_key) = self.type_interner.lookup(prop.type_id) {
1119 result.push(format!(" -> {prop_key:?}"));
1120 }
1121 }
1122 }
1123 }
1124
1125 result.join("\n")
1126 }
1127
1128 #[wasm_bindgen(js_name = debugInterfaceMembers)]
1130 pub fn debug_interface_members(&self, interface_name: &str) -> String {
1131 use parser::syntax_kind_ext;
1132
1133 let arena = self.parser.get_arena();
1134 let mut result = Vec::new();
1135
1136 for i in 0..arena.len() {
1137 let idx = parser::NodeIndex(i as u32);
1138 if let Some(node) = arena.get(idx)
1139 && node.kind == syntax_kind_ext::INTERFACE_DECLARATION
1140 && let Some(interface) = arena.get_interface(node)
1141 && let Some(name_node) = arena.get(interface.name)
1142 && let Some(ident) = arena.get_identifier(name_node)
1143 && ident.escaped_text == interface_name
1144 {
1145 result.push(format!("Interface '{interface_name}' found at node {i}"));
1146 result.push(format!(" members list: {:?}", interface.members.nodes));
1147
1148 for (mi, &member_idx) in interface.members.nodes.iter().enumerate() {
1149 if let Some(member_node) = arena.get(member_idx) {
1150 result.push(format!(
1151 " Member {} (idx {}): kind={}",
1152 mi, member_idx.0, member_node.kind
1153 ));
1154 result.push(format!(" data_index: {}", member_node.data_index));
1155 if let Some(sig) = arena.get_signature(member_node) {
1156 result.push(format!(" name_idx: {:?}", sig.name));
1157 result.push(format!(
1158 " type_annotation_idx: {:?}",
1159 sig.type_annotation
1160 ));
1161
1162 if let Some(name_n) = arena.get(sig.name) {
1164 if let Some(name_id) = arena.get_identifier(name_n) {
1165 result
1166 .push(format!(" name_text: '{}'", name_id.escaped_text));
1167 } else {
1168 result.push(format!(" name_node kind: {}", name_n.kind));
1169 }
1170 }
1171
1172 if let Some(type_n) = arena.get(sig.type_annotation) {
1174 if let Some(type_id) = arena.get_identifier(type_n) {
1175 result
1176 .push(format!(" type_text: '{}'", type_id.escaped_text));
1177 } else {
1178 result.push(format!(" type_node kind: {}", type_n.kind));
1179 }
1180 }
1181 }
1182 }
1183 }
1184 }
1185 }
1186
1187 if result.is_empty() {
1188 format!("Interface '{interface_name}' not found")
1189 } else {
1190 result.join("\n")
1191 }
1192 }
1193
1194 #[wasm_bindgen(js_name = debugScopes)]
1196 pub fn debug_scopes(&self) -> String {
1197 let Some(binder) = &self.binder else {
1198 return "Binder not initialized. Call parseSourceFile and bindSourceFile first."
1199 .to_string();
1200 };
1201
1202 let mut result = Vec::new();
1203 result.push(format!(
1204 "=== Persistent Scopes ({}) ===",
1205 binder.scopes.len()
1206 ));
1207
1208 for (i, scope) in binder.scopes.iter().enumerate() {
1209 result.push(format!(
1210 "\nScope {} (parent: {:?}, kind: {:?}):",
1211 i, scope.parent, scope.kind
1212 ));
1213 result.push(format!(" table entries: {}", scope.table.len()));
1214 for (name, sym_id) in scope.table.iter() {
1215 if let Some(sym) = binder.symbols.get(*sym_id) {
1216 result.push(format!(
1217 " '{}' -> SymbolId({}) [flags: 0x{:x}]",
1218 name, sym_id.0, sym.flags
1219 ));
1220 } else {
1221 result.push(format!(
1222 " '{}' -> SymbolId({}) [MISSING SYMBOL]",
1223 name, sym_id.0
1224 ));
1225 }
1226 }
1227 }
1228
1229 result.push(format!(
1230 "\n=== Node -> Scope Mappings ({}) ===",
1231 binder.node_scope_ids.len()
1232 ));
1233 for (&node_idx, &scope_id) in &binder.node_scope_ids {
1234 result.push(format!(
1235 " NodeIndex({}) -> ScopeId({})",
1236 node_idx, scope_id.0
1237 ));
1238 }
1239
1240 result.push(format!(
1241 "\n=== File Locals ({}) ===",
1242 binder.file_locals.len()
1243 ));
1244 for (name, sym_id) in binder.file_locals.iter() {
1245 result.push(format!(" '{}' -> SymbolId({})", name, sym_id.0));
1246 }
1247
1248 result.join("\n")
1249 }
1250
1251 #[wasm_bindgen(js_name = traceParentChain)]
1253 pub fn trace_parent_chain(&self, pos: u32) -> String {
1254 const IDENTIFIER_KIND: u16 = 80; let arena = self.parser.get_arena();
1256 let binder = match &self.binder {
1257 Some(b) => b,
1258 None => return "Binder not initialized".to_string(),
1259 };
1260
1261 let mut result = Vec::new();
1262 result.push(format!("=== Tracing parent chain for position {pos} ==="));
1263
1264 let mut target_node = None;
1266 for i in 0..arena.len() {
1267 let idx = parser::NodeIndex(i as u32);
1268 if let Some(node) = arena.get(idx)
1269 && node.pos <= pos
1270 && pos < node.end
1271 && node.kind == IDENTIFIER_KIND
1272 {
1273 target_node = Some(idx);
1274 }
1276 }
1277
1278 let start_idx = match target_node {
1279 Some(idx) => idx,
1280 None => return format!("No identifier node found at position {pos}"),
1281 };
1282
1283 result.push(format!("Starting node: {start_idx:?}"));
1284
1285 let mut current = start_idx;
1286 let mut depth = 0;
1287 while !current.is_none() && depth < 20 {
1288 if let Some(node) = arena.get(current) {
1289 let kind_name = format!("kind={}", node.kind);
1290 let scope_info = if let Some(&scope_id) = binder.node_scope_ids.get(¤t.0) {
1291 format!(" -> ScopeId({})", scope_id.0)
1292 } else {
1293 String::new()
1294 };
1295 result.push(format!(
1296 " [{}] NodeIndex({}) {} [pos:{}-{}]{}",
1297 depth, current.0, kind_name, node.pos, node.end, scope_info
1298 ));
1299 }
1300
1301 if let Some(ext) = arena.get_extended(current) {
1302 if ext.parent.is_none() {
1303 result.push(format!(" [{}] Parent is NodeIndex::NONE", depth + 1));
1304 break;
1305 }
1306 current = ext.parent;
1307 } else {
1308 result.push(format!(
1309 " [{}] No extended info for NodeIndex({})",
1310 depth + 1,
1311 current.0
1312 ));
1313 break;
1314 }
1315 depth += 1;
1316 }
1317
1318 result.join("\n")
1319 }
1320
1321 #[wasm_bindgen(js_name = dumpVarDecl)]
1323 pub fn dump_var_decl(&self, var_decl_idx: u32) -> String {
1324 let arena = self.parser.get_arena();
1325 let idx = parser::NodeIndex(var_decl_idx);
1326
1327 let Some(node) = arena.get(idx) else {
1328 return format!("NodeIndex({var_decl_idx}) not found");
1329 };
1330
1331 let Some(var_decl) = arena.get_variable_declaration(node) else {
1332 return format!(
1333 "NodeIndex({}) is not a VARIABLE_DECLARATION (kind={})",
1334 var_decl_idx, node.kind
1335 );
1336 };
1337
1338 format!(
1339 "VariableDeclaration({}):\n name: NodeIndex({})\n type_annotation: NodeIndex({}) (is_none={})\n initializer: NodeIndex({})",
1340 var_decl_idx,
1341 var_decl.name.0,
1342 var_decl.type_annotation.0,
1343 var_decl.type_annotation.is_none(),
1344 var_decl.initializer.0
1345 )
1346 }
1347
1348 #[wasm_bindgen(js_name = dumpAllNodes)]
1350 pub fn dump_all_nodes(&self, start: u32, count: u32) -> String {
1351 let arena = self.parser.get_arena();
1352 let mut result = Vec::new();
1353
1354 for i in start..(start + count).min(arena.len() as u32) {
1355 let idx = parser::NodeIndex(i);
1356 if let Some(node) = arena.get(idx) {
1357 let parent_str = if let Some(ext) = arena.get_extended(idx) {
1358 if ext.parent.is_none() {
1359 "parent:NONE".to_string()
1360 } else {
1361 format!("parent:{}", ext.parent.0)
1362 }
1363 } else {
1364 "no-ext".to_string()
1365 };
1366 let extra = if let Some(ident) = arena.get_identifier(node) {
1368 format!(" \"{}\"", ident.escaped_text)
1369 } else {
1370 String::new()
1371 };
1372 result.push(format!(
1373 " NodeIndex({}) kind={} [pos:{}-{}] {}{}",
1374 i, node.kind, node.pos, node.end, parent_str, extra
1375 ));
1376 }
1377 }
1378
1379 result.join("\n")
1380 }
1381
1382 fn ensure_line_map(&mut self) {
1388 if self.line_map.is_none() {
1389 self.line_map = Some(LineMap::build(self.parser.get_source_text()));
1390 }
1391 }
1392
1393 fn ensure_bound(&mut self) -> Result<(), JsValue> {
1395 if self.source_file_idx.is_none() {
1396 return Err(JsValue::from_str("Source file not parsed"));
1397 }
1398 if self.binder.is_none() {
1399 self.bind_source_file();
1400 }
1401 Ok(())
1402 }
1403
1404 #[wasm_bindgen(js_name = getDefinitionAtPosition)]
1406 pub fn get_definition_at_position(
1407 &mut self,
1408 line: u32,
1409 character: u32,
1410 ) -> Result<JsValue, JsValue> {
1411 self.ensure_bound()?;
1412 self.ensure_line_map();
1413
1414 let root = self
1415 .source_file_idx
1416 .ok_or_else(|| JsValue::from_str("Source file not available"))?;
1417 let binder = self
1418 .binder
1419 .as_ref()
1420 .ok_or_else(|| JsValue::from_str("Binder not available"))?;
1421 let line_map = self
1422 .line_map
1423 .as_ref()
1424 .ok_or_else(|| JsValue::from_str("Line map not available"))?;
1425 let file_name = self.parser.get_file_name().to_string();
1426 let source_text = self.parser.get_source_text();
1427
1428 let provider = GoToDefinition::new(
1429 self.parser.get_arena(),
1430 binder,
1431 line_map,
1432 file_name,
1433 source_text,
1434 );
1435 let pos = Position::new(line, character);
1436
1437 let result =
1438 provider.get_definition_with_scope_cache(root, pos, &mut self.scope_cache, None);
1439 Ok(serde_wasm_bindgen::to_value(&result)?)
1440 }
1441
1442 #[wasm_bindgen(js_name = getReferencesAtPosition)]
1444 pub fn get_references_at_position(
1445 &mut self,
1446 line: u32,
1447 character: u32,
1448 ) -> Result<JsValue, JsValue> {
1449 self.ensure_bound()?;
1450 self.ensure_line_map();
1451
1452 let root = self
1453 .source_file_idx
1454 .ok_or_else(|| JsValue::from_str("Source file not available"))?;
1455 let binder = self
1456 .binder
1457 .as_ref()
1458 .ok_or_else(|| JsValue::from_str("Binder not available"))?;
1459 let line_map = self
1460 .line_map
1461 .as_ref()
1462 .ok_or_else(|| JsValue::from_str("Line map not available"))?;
1463 let file_name = self.parser.get_file_name().to_string();
1464 let source_text = self.parser.get_source_text();
1465
1466 let provider = FindReferences::new(
1467 self.parser.get_arena(),
1468 binder,
1469 line_map,
1470 file_name,
1471 source_text,
1472 );
1473 let pos = Position::new(line, character);
1474
1475 let result =
1476 provider.find_references_with_scope_cache(root, pos, &mut self.scope_cache, None);
1477 Ok(serde_wasm_bindgen::to_value(&result)?)
1478 }
1479
1480 #[wasm_bindgen(js_name = getCompletionsAtPosition)]
1482 pub fn get_completions_at_position(
1483 &mut self,
1484 line: u32,
1485 character: u32,
1486 ) -> Result<JsValue, JsValue> {
1487 self.ensure_bound()?;
1488 self.ensure_line_map();
1489
1490 let root = self
1491 .source_file_idx
1492 .ok_or_else(|| JsValue::from_str("Source file not available"))?;
1493 let binder = self
1494 .binder
1495 .as_ref()
1496 .ok_or_else(|| JsValue::from_str("Binder not available"))?;
1497 let line_map = self
1498 .line_map
1499 .as_ref()
1500 .ok_or_else(|| JsValue::from_str("Line map not available"))?;
1501 let source_text = self.parser.get_source_text();
1502 let file_name = self.parser.get_file_name().to_string();
1503
1504 let provider = Completions::new_with_types(
1505 self.parser.get_arena(),
1506 binder,
1507 line_map,
1508 &self.type_interner,
1509 source_text,
1510 file_name,
1511 );
1512 let pos = Position::new(line, character);
1513
1514 let result = provider.get_completions_with_caches(
1515 root,
1516 pos,
1517 &mut self.type_cache,
1518 &mut self.scope_cache,
1519 None,
1520 );
1521 Ok(serde_wasm_bindgen::to_value(&result)?)
1522 }
1523
1524 #[wasm_bindgen(js_name = getHoverAtPosition)]
1526 pub fn get_hover_at_position(&mut self, line: u32, character: u32) -> Result<JsValue, JsValue> {
1527 self.ensure_bound()?;
1528 self.ensure_line_map();
1529
1530 let root = self
1531 .source_file_idx
1532 .ok_or_else(|| JsValue::from_str("Source file not available"))?;
1533 let binder = self
1534 .binder
1535 .as_ref()
1536 .ok_or_else(|| JsValue::from_str("Binder not available"))?;
1537 let line_map = self
1538 .line_map
1539 .as_ref()
1540 .ok_or_else(|| JsValue::from_str("Line map not available"))?;
1541 let source_text = self.parser.get_source_text();
1542 let file_name = self.parser.get_file_name().to_string();
1543
1544 let provider = HoverProvider::new(
1545 self.parser.get_arena(),
1546 binder,
1547 line_map,
1548 &self.type_interner,
1549 source_text,
1550 file_name,
1551 );
1552 let pos = Position::new(line, character);
1553
1554 let result = provider.get_hover_with_scope_cache(
1555 root,
1556 pos,
1557 &mut self.type_cache,
1558 &mut self.scope_cache,
1559 None,
1560 );
1561 Ok(serde_wasm_bindgen::to_value(&result)?)
1562 }
1563
1564 #[wasm_bindgen(js_name = getSignatureHelpAtPosition)]
1566 pub fn get_signature_help_at_position(
1567 &mut self,
1568 line: u32,
1569 character: u32,
1570 ) -> Result<JsValue, JsValue> {
1571 self.ensure_bound()?;
1572 self.ensure_line_map();
1573
1574 let root = self
1575 .source_file_idx
1576 .ok_or_else(|| JsValue::from_str("Source file not available"))?;
1577 let binder = self
1578 .binder
1579 .as_ref()
1580 .ok_or_else(|| JsValue::from_str("Binder not available"))?;
1581 let line_map = self
1582 .line_map
1583 .as_ref()
1584 .ok_or_else(|| JsValue::from_str("Line map not available"))?;
1585 let source_text = self.parser.get_source_text();
1586 let file_name = self.parser.get_file_name().to_string();
1587
1588 let provider = SignatureHelpProvider::new(
1589 self.parser.get_arena(),
1590 binder,
1591 line_map,
1592 &self.type_interner,
1593 source_text,
1594 file_name,
1595 );
1596 let pos = Position::new(line, character);
1597
1598 let result = provider.get_signature_help_with_scope_cache(
1599 root,
1600 pos,
1601 &mut self.type_cache,
1602 &mut self.scope_cache,
1603 None,
1604 );
1605 Ok(serde_wasm_bindgen::to_value(&result)?)
1606 }
1607
1608 #[wasm_bindgen(js_name = getDocumentSymbols)]
1610 pub fn get_document_symbols(&mut self) -> Result<JsValue, JsValue> {
1611 self.ensure_bound()?;
1612 self.ensure_line_map();
1613
1614 let root = self
1615 .source_file_idx
1616 .ok_or_else(|| JsValue::from_str("Source file not available"))?;
1617 let line_map = self
1618 .line_map
1619 .as_ref()
1620 .ok_or_else(|| JsValue::from_str("Line map not available"))?;
1621 let source_text = self.parser.get_source_text();
1622
1623 let provider = DocumentSymbolProvider::new(self.parser.get_arena(), line_map, source_text);
1624
1625 let result = provider.get_document_symbols(root);
1626 Ok(serde_wasm_bindgen::to_value(&result)?)
1627 }
1628
1629 #[wasm_bindgen(js_name = getSemanticTokens)]
1631 pub fn get_semantic_tokens(&mut self) -> Result<Vec<u32>, JsValue> {
1632 self.ensure_bound()?;
1633 self.ensure_line_map();
1634
1635 let root = self
1636 .source_file_idx
1637 .ok_or_else(|| JsValue::from_str("Source file not available"))?;
1638 let binder = self
1639 .binder
1640 .as_ref()
1641 .ok_or_else(|| JsValue::from_str("Binder not available"))?;
1642 let line_map = self
1643 .line_map
1644 .as_ref()
1645 .ok_or_else(|| JsValue::from_str("Line map not available"))?;
1646 let source_text = self.parser.get_source_text();
1647
1648 let mut provider =
1649 SemanticTokensProvider::new(self.parser.get_arena(), binder, line_map, source_text);
1650
1651 Ok(provider.get_semantic_tokens(root))
1652 }
1653
1654 #[wasm_bindgen(js_name = prepareRename)]
1656 pub fn prepare_rename(&mut self, line: u32, character: u32) -> Result<JsValue, JsValue> {
1657 self.ensure_bound()?;
1658 self.ensure_line_map();
1659
1660 let binder = self
1661 .binder
1662 .as_ref()
1663 .ok_or_else(|| JsValue::from_str("Internal error: binder not available"))?;
1664 let line_map = self
1665 .line_map
1666 .as_ref()
1667 .ok_or_else(|| JsValue::from_str("Internal error: line map not available"))?;
1668 let file_name = self.parser.get_file_name().to_string();
1669 let source_text = self.parser.get_source_text();
1670
1671 let provider = RenameProvider::new(
1672 self.parser.get_arena(),
1673 binder,
1674 line_map,
1675 file_name,
1676 source_text,
1677 );
1678 let pos = Position::new(line, character);
1679
1680 let result = provider.prepare_rename(pos);
1681 Ok(serde_wasm_bindgen::to_value(&result)?)
1682 }
1683
1684 #[wasm_bindgen(js_name = getRenameEdits)]
1686 pub fn get_rename_edits(
1687 &mut self,
1688 line: u32,
1689 character: u32,
1690 new_name: String,
1691 ) -> Result<JsValue, JsValue> {
1692 self.ensure_bound()?;
1693 self.ensure_line_map();
1694
1695 let root = self
1696 .source_file_idx
1697 .ok_or_else(|| JsValue::from_str("Source file not available"))?;
1698 let binder = self
1699 .binder
1700 .as_ref()
1701 .ok_or_else(|| JsValue::from_str("Binder not available"))?;
1702 let line_map = self
1703 .line_map
1704 .as_ref()
1705 .ok_or_else(|| JsValue::from_str("Line map not available"))?;
1706 let file_name = self.parser.get_file_name().to_string();
1707 let source_text = self.parser.get_source_text();
1708
1709 let provider = RenameProvider::new(
1710 self.parser.get_arena(),
1711 binder,
1712 line_map,
1713 file_name,
1714 source_text,
1715 );
1716 let pos = Position::new(line, character);
1717
1718 match provider.provide_rename_edits_with_scope_cache(
1719 root,
1720 pos,
1721 new_name,
1722 &mut self.scope_cache,
1723 None,
1724 ) {
1725 Ok(edit) => Ok(serde_wasm_bindgen::to_value(&edit)?),
1726 Err(e) => Err(JsValue::from_str(&e)),
1727 }
1728 }
1729
1730 #[wasm_bindgen(js_name = getCodeActions)]
1732 pub fn get_code_actions(
1733 &mut self,
1734 start_line: u32,
1735 start_char: u32,
1736 end_line: u32,
1737 end_char: u32,
1738 ) -> Result<JsValue, JsValue> {
1739 self.ensure_bound()?;
1740 self.ensure_line_map();
1741
1742 let root = self
1743 .source_file_idx
1744 .ok_or_else(|| JsValue::from_str("Source file not available"))?;
1745 let binder = self
1746 .binder
1747 .as_ref()
1748 .ok_or_else(|| JsValue::from_str("Binder not available"))?;
1749 let line_map = self
1750 .line_map
1751 .as_ref()
1752 .ok_or_else(|| JsValue::from_str("Line map not available"))?;
1753 let file_name = self.parser.get_file_name().to_string();
1754 let source_text = self.parser.get_source_text();
1755
1756 let provider = CodeActionProvider::new(
1757 self.parser.get_arena(),
1758 binder,
1759 line_map,
1760 file_name,
1761 source_text,
1762 );
1763
1764 let range = Range::new(
1765 Position::new(start_line, start_char),
1766 Position::new(end_line, end_char),
1767 );
1768
1769 let context = CodeActionContext {
1770 diagnostics: Vec::new(),
1771 only: None,
1772 import_candidates: Vec::new(),
1773 };
1774
1775 let result = provider.provide_code_actions(root, range, context);
1776 Ok(serde_wasm_bindgen::to_value(&result)?)
1777 }
1778
1779 #[wasm_bindgen(js_name = getCodeActionsWithContext)]
1781 pub fn get_code_actions_with_context(
1782 &mut self,
1783 start_line: u32,
1784 start_char: u32,
1785 end_line: u32,
1786 end_char: u32,
1787 context: JsValue,
1788 ) -> Result<JsValue, JsValue> {
1789 self.ensure_bound()?;
1790 self.ensure_line_map();
1791
1792 let context = if context.is_null() || context.is_undefined() {
1793 CodeActionContext {
1794 diagnostics: Vec::new(),
1795 only: None,
1796 import_candidates: Vec::new(),
1797 }
1798 } else {
1799 let context_input: CodeActionContextInput = serde_wasm_bindgen::from_value(context)?;
1800 let import_candidates = context_input
1801 .import_candidates
1802 .into_iter()
1803 .map(ImportCandidate::try_from)
1804 .collect::<Result<Vec<_>, _>>()?;
1805 CodeActionContext {
1806 diagnostics: context_input.diagnostics,
1807 only: context_input.only,
1808 import_candidates,
1809 }
1810 };
1811
1812 let root = self
1813 .source_file_idx
1814 .ok_or_else(|| JsValue::from_str("Source file not available"))?;
1815 let binder = self
1816 .binder
1817 .as_ref()
1818 .ok_or_else(|| JsValue::from_str("Binder not available"))?;
1819 let line_map = self
1820 .line_map
1821 .as_ref()
1822 .ok_or_else(|| JsValue::from_str("Line map not available"))?;
1823 let file_name = self.parser.get_file_name().to_string();
1824 let source_text = self.parser.get_source_text();
1825
1826 let provider = CodeActionProvider::new(
1827 self.parser.get_arena(),
1828 binder,
1829 line_map,
1830 file_name,
1831 source_text,
1832 );
1833
1834 let range = Range::new(
1835 Position::new(start_line, start_char),
1836 Position::new(end_line, end_char),
1837 );
1838
1839 let result = provider.provide_code_actions(root, range, context);
1840 Ok(serde_wasm_bindgen::to_value(&result)?)
1841 }
1842
1843 #[wasm_bindgen(js_name = getLspDiagnostics)]
1845 pub fn get_lsp_diagnostics(&mut self) -> Result<JsValue, JsValue> {
1846 self.ensure_bound()?;
1847 self.ensure_line_map();
1848
1849 let root = self
1850 .source_file_idx
1851 .ok_or_else(|| JsValue::from_str("Source file not available"))?;
1852 let binder = self
1853 .binder
1854 .as_ref()
1855 .ok_or_else(|| JsValue::from_str("Binder not available"))?;
1856 let line_map = self
1857 .line_map
1858 .as_ref()
1859 .ok_or_else(|| JsValue::from_str("Line map not available"))?;
1860 let file_name = self.parser.get_file_name().to_string();
1861 let source_text = self.parser.get_source_text();
1862
1863 let checker_options = self.compiler_options.to_checker_options();
1865
1866 let mut checker = if let Some(cache) = self.type_cache.take() {
1867 CheckerState::with_cache_and_options(
1868 self.parser.get_arena(),
1869 binder,
1870 &self.type_interner,
1871 file_name,
1872 cache,
1873 &checker_options,
1874 )
1875 } else {
1876 CheckerState::with_options(
1877 self.parser.get_arena(),
1878 binder,
1879 &self.type_interner,
1880 file_name,
1881 &checker_options,
1882 )
1883 };
1884
1885 checker.check_source_file(root);
1886
1887 let lsp_diagnostics: Vec<_> = checker
1888 .ctx
1889 .diagnostics
1890 .iter()
1891 .map(|diag| convert_diagnostic(diag, line_map, source_text))
1892 .collect();
1893
1894 self.type_cache = Some(checker.extract_cache());
1895
1896 Ok(serde_wasm_bindgen::to_value(&lsp_diagnostics)?)
1897 }
1898}
1899
1900#[wasm_bindgen(js_name = createParser)]
1903pub fn create_parser(file_name: String, source_text: String) -> Parser {
1904 Parser::new(file_name, source_text)
1905}
1906
1907use crate::parallel::{
1912 BindResult, MergedProgram, check_files_parallel, merge_bind_results, parse_and_bind_parallel,
1913};
1914
1915#[derive(serde::Serialize)]
1917#[serde(rename_all = "camelCase")]
1918struct FileCheckResultJson {
1919 file_name: String,
1920 parse_diagnostics: Vec<ParseDiagnosticJson>,
1921 check_diagnostics: Vec<CheckDiagnosticJson>,
1922}
1923
1924#[derive(serde::Serialize)]
1925#[serde(rename_all = "camelCase")]
1926struct ParseDiagnosticJson {
1927 message: String,
1928 start: u32,
1929 length: u32,
1930 code: u32,
1931}
1932
1933#[derive(serde::Serialize)]
1934#[serde(rename_all = "camelCase")]
1935struct CheckDiagnosticJson {
1936 message_text: String,
1937 code: u32,
1938 start: u32,
1939 length: u32,
1940 category: String,
1941}
1942
1943#[wasm_bindgen]
1957pub struct WasmProgram {
1958 files: Vec<(String, String)>,
1960 merged: Option<MergedProgram>,
1962 bind_results: Option<Vec<BindResult>>,
1964 lib_files: Vec<(String, String)>,
1966 compiler_options: CompilerOptions,
1968}
1969
1970impl Default for WasmProgram {
1971 fn default() -> Self {
1972 Self::new()
1973 }
1974}
1975
1976#[wasm_bindgen]
1977impl WasmProgram {
1978 #[wasm_bindgen(constructor)]
1980 pub fn new() -> Self {
1981 Self {
1982 files: Vec::new(),
1983 lib_files: Vec::new(),
1984 merged: None,
1985 bind_results: None,
1986 compiler_options: CompilerOptions::default(),
1987 }
1988 }
1989
1990 #[wasm_bindgen(js_name = addFile)]
1997 pub fn add_file(&mut self, file_name: String, source_text: String) {
1998 self.merged = None;
2000 self.bind_results = None;
2001
2002 if file_name.ends_with("package.json") {
2004 return;
2005 }
2006
2007 self.files.push((file_name, source_text));
2008 }
2009
2010 #[wasm_bindgen(js_name = addLibFile)]
2025 pub fn add_lib_file(&mut self, file_name: String, source_text: String) {
2026 self.merged = None;
2028 self.bind_results = None;
2029
2030 self.lib_files.push((file_name, source_text));
2031 }
2032
2033 #[wasm_bindgen(js_name = setCompilerOptions)]
2038 pub fn set_compiler_options(&mut self, options_json: &str) -> Result<(), JsValue> {
2039 match serde_json::from_str::<CompilerOptions>(options_json) {
2040 Ok(options) => {
2041 self.compiler_options = options;
2042 self.merged = None;
2044 self.bind_results = None;
2045 Ok(())
2046 }
2047 Err(e) => Err(JsValue::from_str(&format!(
2048 "Failed to parse compiler options: {e}"
2049 ))),
2050 }
2051 }
2052
2053 #[allow(clippy::missing_const_for_fn)] #[wasm_bindgen(js_name = getFileCount)]
2056 pub fn get_file_count(&self) -> usize {
2057 self.files.len()
2058 }
2059
2060 #[wasm_bindgen]
2062 pub fn clear(&mut self) {
2063 self.files.clear();
2064 self.lib_files.clear();
2065 self.merged = None;
2066 self.bind_results = None;
2067 }
2068
2069 #[wasm_bindgen(js_name = checkAll)]
2080 pub fn check_all(&mut self) -> String {
2081 if self.files.is_empty() && self.lib_files.is_empty() {
2082 return r#"{"files":[],"stats":{"totalFiles":0,"totalDiagnostics":0}}"#.to_string();
2083 }
2084
2085 let lib_file_objects: Vec<Arc<lib_loader::LibFile>> = self
2088 .lib_files
2089 .iter()
2090 .map(|(file_name, source_text)| {
2091 get_or_create_lib_file(file_name.clone(), source_text.clone())
2092 })
2093 .collect();
2094
2095 let bind_results = if !lib_file_objects.is_empty() {
2097 use crate::parallel;
2099 parallel::parse_and_bind_parallel_with_libs(self.files.clone(), &lib_file_objects)
2100 } else {
2101 parse_and_bind_parallel(self.files.clone())
2103 };
2104
2105 let parse_diags: Vec<Vec<_>> = bind_results
2107 .iter()
2108 .map(|r| r.parse_diagnostics.clone())
2109 .collect();
2110 let file_names: Vec<String> = bind_results.iter().map(|r| r.file_name.clone()).collect();
2111
2112 let merged = merge_bind_results(bind_results);
2114
2115 let checker_options = self.compiler_options.to_checker_options();
2117 let check_result = check_files_parallel(&merged, &checker_options, &lib_file_objects);
2118
2119 let mut file_results: Vec<FileCheckResultJson> = Vec::new();
2121 let mut total_diagnostics = 0;
2122
2123 for (i, file_name) in file_names.iter().enumerate() {
2124 let parse_diagnostics: Vec<ParseDiagnosticJson> = parse_diags[i]
2125 .iter()
2126 .map(|d| ParseDiagnosticJson {
2127 message: d.message.clone(),
2128 start: d.start,
2129 length: d.length,
2130 code: d.code,
2131 })
2132 .collect();
2133
2134 let check_diagnostics: Vec<CheckDiagnosticJson> = check_result
2136 .file_results
2137 .iter()
2138 .find(|r| &r.file_name == file_name)
2139 .map(|r| {
2140 r.diagnostics
2141 .iter()
2142 .map(|d| CheckDiagnosticJson {
2143 message_text: d.message_text.clone(),
2144 code: d.code,
2145 start: d.start,
2146 length: d.length,
2147 category: format!("{:?}", d.category),
2148 })
2149 .collect()
2150 })
2151 .unwrap_or_default();
2152
2153 total_diagnostics += parse_diagnostics.len() + check_diagnostics.len();
2154
2155 file_results.push(FileCheckResultJson {
2156 file_name: file_name.clone(),
2157 parse_diagnostics,
2158 check_diagnostics,
2159 });
2160 }
2161
2162 self.merged = Some(merged);
2164
2165 let result = serde_json::json!({
2166 "files": file_results,
2167 "stats": {
2168 "totalFiles": file_names.len(),
2169 "totalDiagnostics": total_diagnostics,
2170 }
2171 });
2172
2173 serde_json::to_string(&result).unwrap_or_else(|_| "{}".to_string())
2174 }
2175
2176 #[wasm_bindgen(js_name = getDiagnosticCodes)]
2180 pub fn get_diagnostic_codes(&mut self) -> String {
2181 if self.files.is_empty() && self.lib_files.is_empty() {
2182 return "{}".to_string();
2183 }
2184
2185 let lib_file_objects: Vec<Arc<lib_loader::LibFile>> = self
2188 .lib_files
2189 .iter()
2190 .map(|(file_name, source_text)| {
2191 get_or_create_lib_file(file_name.clone(), source_text.clone())
2192 })
2193 .collect();
2194
2195 let bind_results = if !lib_file_objects.is_empty() {
2197 use crate::parallel;
2198 parallel::parse_and_bind_parallel_with_libs(self.files.clone(), &lib_file_objects)
2199 } else {
2200 parse_and_bind_parallel(self.files.clone())
2201 };
2202
2203 let mut file_codes: FxHashMap<String, Vec<u32>> = FxHashMap::default();
2205 for result in &bind_results {
2206 let codes: Vec<u32> = result.parse_diagnostics.iter().map(|d| d.code).collect();
2207 file_codes.insert(result.file_name.clone(), codes);
2208 }
2209
2210 let merged = merge_bind_results(bind_results);
2212 let checker_options = self.compiler_options.to_checker_options();
2213 let check_result = check_files_parallel(&merged, &checker_options, &lib_file_objects);
2214
2215 for file_result in &check_result.file_results {
2217 let entry = file_codes.entry(file_result.file_name.clone()).or_default();
2218 for diag in &file_result.diagnostics {
2219 entry.push(diag.code);
2220 }
2221 }
2222
2223 self.merged = Some(merged);
2225
2226 serde_json::to_string(&file_codes).unwrap_or_else(|_| "{}".to_string())
2227 }
2228
2229 #[wasm_bindgen(js_name = getAllDiagnosticCodes)]
2234 pub fn get_all_diagnostic_codes(&mut self) -> Vec<u32> {
2235 if self.files.is_empty() && self.lib_files.is_empty() {
2236 return Vec::new();
2237 }
2238
2239 let lib_file_objects: Vec<Arc<lib_loader::LibFile>> = self
2242 .lib_files
2243 .iter()
2244 .map(|(file_name, source_text)| {
2245 get_or_create_lib_file(file_name.clone(), source_text.clone())
2246 })
2247 .collect();
2248
2249 let bind_results = if !lib_file_objects.is_empty() {
2251 use crate::parallel;
2252 parallel::parse_and_bind_parallel_with_libs(self.files.clone(), &lib_file_objects)
2253 } else {
2254 parse_and_bind_parallel(self.files.clone())
2255 };
2256
2257 let mut all_codes: Vec<u32> = Vec::new();
2259 for result in &bind_results {
2260 for diag in &result.parse_diagnostics {
2261 all_codes.push(diag.code);
2262 }
2263 }
2264
2265 let merged = merge_bind_results(bind_results);
2267 let checker_options = self.compiler_options.to_checker_options();
2268 let check_result = check_files_parallel(&merged, &checker_options, &lib_file_objects);
2269
2270 for file_result in &check_result.file_results {
2272 for diag in &file_result.diagnostics {
2273 all_codes.push(diag.code);
2274 }
2275 }
2276
2277 self.merged = Some(merged);
2279
2280 all_codes
2281 }
2282}
2283
2284#[wasm_bindgen(js_name = createProgram)]
2286pub fn create_program() -> WasmProgram {
2287 WasmProgram::new()
2288}
2289
2290#[wasm_bindgen]
2297#[repr(i32)]
2298#[derive(Clone, Copy, Debug, PartialEq, Eq)]
2299pub enum Comparison {
2300 LessThan = -1,
2301 EqualTo = 0,
2302 GreaterThan = 1,
2303}
2304
2305#[wasm_bindgen(js_name = compareStringsCaseSensitive)]
2315pub fn compare_strings_case_sensitive(a: Option<String>, b: Option<String>) -> Comparison {
2316 match (a, b) {
2317 (None, None) => Comparison::EqualTo,
2318 (None, Some(_)) => Comparison::LessThan,
2319 (Some(_), None) => Comparison::GreaterThan,
2320 (Some(a), Some(b)) => match a.cmp(&b) {
2321 std::cmp::Ordering::Equal => Comparison::EqualTo,
2322 std::cmp::Ordering::Less => Comparison::LessThan,
2323 std::cmp::Ordering::Greater => Comparison::GreaterThan,
2324 },
2325 }
2326}
2327
2328#[wasm_bindgen(js_name = compareStringsCaseInsensitive)]
2335pub fn compare_strings_case_insensitive(a: Option<String>, b: Option<String>) -> Comparison {
2336 match (a, b) {
2337 (None, None) => Comparison::EqualTo,
2338 (None, Some(_)) => Comparison::LessThan,
2339 (Some(_), None) => Comparison::GreaterThan,
2340 (Some(a), Some(b)) => {
2341 if a == b {
2342 return Comparison::EqualTo;
2343 }
2344 compare_strings_case_insensitive_iter(&a, &b)
2346 }
2347 }
2348}
2349
2350#[inline]
2353fn compare_strings_case_insensitive_iter(a: &str, b: &str) -> Comparison {
2354 use std::cmp::Ordering;
2355
2356 let mut a_chars = a.chars().flat_map(char::to_uppercase);
2357 let mut b_chars = b.chars().flat_map(char::to_uppercase);
2358
2359 loop {
2360 match (a_chars.next(), b_chars.next()) {
2361 (None, None) => return Comparison::EqualTo,
2362 (None, Some(_)) => return Comparison::LessThan,
2363 (Some(_), None) => return Comparison::GreaterThan,
2364 (Some(a_char), Some(b_char)) => match a_char.cmp(&b_char) {
2365 Ordering::Less => return Comparison::LessThan,
2366 Ordering::Greater => return Comparison::GreaterThan,
2367 Ordering::Equal => continue,
2368 },
2369 }
2370 }
2371}
2372
2373#[wasm_bindgen(js_name = compareStringsCaseInsensitiveEslintCompatible)]
2379pub fn compare_strings_case_insensitive_eslint_compatible(
2380 a: Option<String>,
2381 b: Option<String>,
2382) -> Comparison {
2383 match (a, b) {
2384 (None, None) => Comparison::EqualTo,
2385 (None, Some(_)) => Comparison::LessThan,
2386 (Some(_), None) => Comparison::GreaterThan,
2387 (Some(a), Some(b)) => {
2388 if a == b {
2389 return Comparison::EqualTo;
2390 }
2391 compare_strings_case_insensitive_lower_iter(&a, &b)
2393 }
2394 }
2395}
2396
2397#[inline]
2400fn compare_strings_case_insensitive_lower_iter(a: &str, b: &str) -> Comparison {
2401 use std::cmp::Ordering;
2402
2403 let mut a_chars = a.chars().flat_map(char::to_lowercase);
2404 let mut b_chars = b.chars().flat_map(char::to_lowercase);
2405
2406 loop {
2407 match (a_chars.next(), b_chars.next()) {
2408 (None, None) => return Comparison::EqualTo,
2409 (None, Some(_)) => return Comparison::LessThan,
2410 (Some(_), None) => return Comparison::GreaterThan,
2411 (Some(a_char), Some(b_char)) => match a_char.cmp(&b_char) {
2412 Ordering::Less => return Comparison::LessThan,
2413 Ordering::Greater => return Comparison::GreaterThan,
2414 Ordering::Equal => continue,
2415 },
2416 }
2417 }
2418}
2419
2420#[wasm_bindgen(js_name = equateStringsCaseSensitive)]
2422pub fn equate_strings_case_sensitive(a: &str, b: &str) -> bool {
2423 a == b
2424}
2425
2426#[wasm_bindgen(js_name = equateStringsCaseInsensitive)]
2429pub fn equate_strings_case_insensitive(a: &str, b: &str) -> bool {
2430 if a.len() != b.len() {
2431 }
2434 a.chars()
2435 .flat_map(char::to_uppercase)
2436 .eq(b.chars().flat_map(char::to_uppercase))
2437}
2438
2439pub const DIRECTORY_SEPARATOR: char = '/';
2445
2446pub const ALT_DIRECTORY_SEPARATOR: char = '\\';
2448
2449#[allow(clippy::missing_const_for_fn)] #[wasm_bindgen(js_name = isAnyDirectorySeparator)]
2452pub fn is_any_directory_separator(char_code: u32) -> bool {
2453 char_code == DIRECTORY_SEPARATOR as u32 || char_code == ALT_DIRECTORY_SEPARATOR as u32
2454}
2455
2456#[wasm_bindgen(js_name = normalizeSlashes)]
2458pub fn normalize_slashes(path: &str) -> String {
2459 if path.contains('\\') {
2460 path.replace('\\', "/")
2461 } else {
2462 path.to_string()
2463 }
2464}
2465
2466#[wasm_bindgen(js_name = hasTrailingDirectorySeparator)]
2468pub fn has_trailing_directory_separator(path: &str) -> bool {
2469 let last_char = match path.chars().last() {
2470 Some(c) => c,
2471 None => return false,
2472 };
2473 last_char == DIRECTORY_SEPARATOR || last_char == ALT_DIRECTORY_SEPARATOR
2474}
2475
2476#[wasm_bindgen(js_name = pathIsRelative)]
2478pub fn path_is_relative(path: &str) -> bool {
2479 if path.starts_with("./") || path.starts_with(".\\") || path == "." {
2481 return true;
2482 }
2483 if path.starts_with("../") || path.starts_with("..\\") || path == ".." {
2484 return true;
2485 }
2486 false
2487}
2488
2489#[wasm_bindgen(js_name = removeTrailingDirectorySeparator)]
2492pub fn remove_trailing_directory_separator(path: &str) -> String {
2493 if !has_trailing_directory_separator(path) || path.len() <= 1 {
2494 return path.to_string();
2495 }
2496 path.strip_suffix(DIRECTORY_SEPARATOR)
2498 .or_else(|| path.strip_suffix(ALT_DIRECTORY_SEPARATOR))
2499 .unwrap_or(path)
2500 .to_string()
2501}
2502
2503#[wasm_bindgen(js_name = ensureTrailingDirectorySeparator)]
2505pub fn ensure_trailing_directory_separator(path: &str) -> String {
2506 if has_trailing_directory_separator(path) {
2507 path.to_string()
2508 } else {
2509 format!("{path}/")
2510 }
2511}
2512
2513#[wasm_bindgen(js_name = hasExtension)]
2515pub fn has_extension(file_name: &str) -> bool {
2516 get_base_file_name(file_name).contains('.')
2517}
2518
2519#[wasm_bindgen(js_name = getBaseFileName)]
2522pub fn get_base_file_name(path: &str) -> String {
2523 let path = normalize_slashes(path);
2524 let path = if has_trailing_directory_separator(&path) && path.len() > 1 {
2526 path.strip_suffix(DIRECTORY_SEPARATOR)
2527 .or_else(|| path.strip_suffix(ALT_DIRECTORY_SEPARATOR))
2528 .unwrap_or(&path)
2529 } else {
2530 &path
2531 };
2532 match path.rfind('/') {
2534 Some(idx) => path[idx + 1..].to_string(),
2535 None => path.to_string(),
2536 }
2537}
2538
2539#[wasm_bindgen(js_name = fileExtensionIs)]
2541pub fn file_extension_is(path: &str, extension: &str) -> bool {
2542 path.len() > extension.len() && path.ends_with(extension)
2543}
2544
2545#[wasm_bindgen(js_name = toFileNameLowerCase)]
2559pub fn to_file_name_lower_case(x: &str) -> String {
2560 let needs_conversion = x.chars().any(|c| {
2569 !matches!(c,
2570 '\u{0130}' | '\u{0131}' | '\u{00DF}' | 'a'..='z' | '0'..='9' | '\\' | '/' | ':' | '-' | '_' | '.' | ' ' )
2574 });
2575
2576 if !needs_conversion {
2577 return x.to_string();
2578 }
2579
2580 x.to_lowercase()
2582}
2583
2584use crate::char_codes::CharacterCodes;
2589
2590#[allow(clippy::missing_const_for_fn)] #[wasm_bindgen(js_name = isLineBreak)]
2593pub fn is_line_break(ch: u32) -> bool {
2594 ch == CharacterCodes::LINE_FEED
2595 || ch == CharacterCodes::CARRIAGE_RETURN
2596 || ch == CharacterCodes::LINE_SEPARATOR
2597 || ch == CharacterCodes::PARAGRAPH_SEPARATOR
2598}
2599
2600#[wasm_bindgen(js_name = isWhiteSpaceSingleLine)]
2602pub fn is_white_space_single_line(ch: u32) -> bool {
2603 ch == CharacterCodes::SPACE
2604 || ch == CharacterCodes::TAB
2605 || ch == CharacterCodes::VERTICAL_TAB
2606 || ch == CharacterCodes::FORM_FEED
2607 || ch == CharacterCodes::NON_BREAKING_SPACE
2608 || ch == CharacterCodes::NEXT_LINE
2609 || ch == CharacterCodes::OGHAM
2610 || (CharacterCodes::EN_QUAD..=CharacterCodes::ZERO_WIDTH_SPACE).contains(&ch)
2611 || ch == CharacterCodes::NARROW_NO_BREAK_SPACE
2612 || ch == CharacterCodes::MATHEMATICAL_SPACE
2613 || ch == CharacterCodes::IDEOGRAPHIC_SPACE
2614 || ch == CharacterCodes::BYTE_ORDER_MARK
2615}
2616
2617#[wasm_bindgen(js_name = isWhiteSpaceLike)]
2619pub fn is_white_space_like(ch: u32) -> bool {
2620 is_white_space_single_line(ch) || is_line_break(ch)
2621}
2622
2623#[wasm_bindgen(js_name = isDigit)]
2625pub fn is_digit(ch: u32) -> bool {
2626 (CharacterCodes::_0..=CharacterCodes::_9).contains(&ch)
2627}
2628
2629#[wasm_bindgen(js_name = isOctalDigit)]
2631pub fn is_octal_digit(ch: u32) -> bool {
2632 (CharacterCodes::_0..=CharacterCodes::_7).contains(&ch)
2633}
2634
2635#[wasm_bindgen(js_name = isHexDigit)]
2637pub fn is_hex_digit(ch: u32) -> bool {
2638 is_digit(ch)
2639 || (CharacterCodes::UPPER_A..=CharacterCodes::UPPER_F).contains(&ch)
2640 || (CharacterCodes::LOWER_A..=CharacterCodes::LOWER_F).contains(&ch)
2641}
2642
2643#[wasm_bindgen(js_name = isASCIILetter)]
2645pub fn is_ascii_letter(ch: u32) -> bool {
2646 (CharacterCodes::UPPER_A..=CharacterCodes::UPPER_Z).contains(&ch)
2647 || (CharacterCodes::LOWER_A..=CharacterCodes::LOWER_Z).contains(&ch)
2648}
2649
2650#[wasm_bindgen(js_name = isWordCharacter)]
2652pub fn is_word_character(ch: u32) -> bool {
2653 is_ascii_letter(ch) || is_digit(ch) || ch == CharacterCodes::UNDERSCORE
2654}
2655
2656#[cfg(test)]
2662#[path = "../tests/asi_conformance_tests.rs"]
2663mod asi_conformance_tests;
2664
2665#[cfg(test)]
2666#[path = "../tests/debug_asi.rs"]
2667mod debug_asi;
2668
2669#[cfg(test)]
2671#[path = "../tests/p1_error_recovery_tests.rs"]
2672mod p1_error_recovery_tests;
2673
2674#[cfg(test)]
2679#[path = "../crates/tsz-checker/tests/constructor_accessibility.rs"]
2680mod constructor_accessibility;
2681
2682#[cfg(test)]
2684#[path = "../crates/tsz-checker/tests/void_return_exception.rs"]
2685mod void_return_exception;
2686
2687#[cfg(test)]
2689#[path = "../crates/tsz-checker/tests/any_propagation.rs"]
2690mod any_propagation;
2691
2692#[cfg(test)]
2694#[path = "../crates/tsz-checker/tests/any_propagation_tests.rs"]
2695mod any_propagation_tests;
2696#[cfg(test)]
2697#[path = "../crates/tsz-checker/tests/const_assertion_tests.rs"]
2698mod const_assertion_tests;
2699#[cfg(test)]
2700#[path = "../crates/tsz-checker/tests/contextual_typing_tests.rs"]
2701mod contextual_typing_tests;
2702#[cfg(test)]
2703#[path = "../crates/tsz-checker/tests/freshness_stripping_tests.rs"]
2704mod freshness_stripping_tests;
2705#[cfg(test)]
2706#[path = "../crates/tsz-checker/tests/function_bivariance.rs"]
2707mod function_bivariance;
2708#[cfg(test)]
2709#[path = "../crates/tsz-checker/tests/global_type_tests.rs"]
2710mod global_type_tests;
2711#[cfg(test)]
2712#[path = "../crates/tsz-checker/tests/symbol_resolution_tests.rs"]
2713mod symbol_resolution_tests;
2714#[cfg(test)]
2715#[path = "../crates/tsz-checker/tests/ts2304_tests.rs"]
2716mod ts2304_tests;
2717#[cfg(test)]
2718#[path = "../crates/tsz-checker/tests/ts2305_tests.rs"]
2719mod ts2305_tests;
2720#[cfg(test)]
2721#[path = "../crates/tsz-checker/tests/ts2306_tests.rs"]
2722mod ts2306_tests;
2723#[cfg(test)]
2724#[path = "../crates/tsz-checker/tests/widening_integration_tests.rs"]
2725mod widening_integration_tests;