1use std::rc::Rc;
4
5use crate::FlowAnalyzer;
6use crate::diagnostics::Diagnostic;
7use crate::query_boundaries::definite_assignment::should_report_variable_use_before_assignment;
8use crate::state::{CheckerState, MAX_TREE_WALK_ITERATIONS};
9use tsz_binder::SymbolId;
10use tsz_parser::parser::NodeIndex;
11use tsz_parser::parser::node::NodeAccess;
12use tsz_parser::parser::syntax_kind_ext;
13use tsz_scanner::SyntaxKind;
14use tsz_solver::TypeId;
15
16impl<'a> CheckerState<'a> {
17 pub fn check_flow_usage(
39 &mut self,
40 idx: NodeIndex,
41 declared_type: TypeId,
42 sym_id: SymbolId,
43 ) -> TypeId {
44 use tracing::trace;
45
46 trace!(?idx, ?declared_type, ?sym_id, "check_flow_usage called");
47
48 if !self.symbol_participates_in_flow_analysis(sym_id) {
52 trace!("Symbol does not participate in flow analysis, returning declared type");
53 return declared_type;
54 }
55
56 if self.should_skip_flow_narrowing_for_const_literal_binding(sym_id) {
60 return declared_type;
61 }
62
63 if should_report_variable_use_before_assignment(self, idx, declared_type, sym_id) {
65 self.emit_definite_assignment_error(idx, sym_id);
67 trace!("Definite assignment error, returning declared type");
69 return declared_type;
70 }
71
72 trace!("Applying flow narrowing");
74 let result = self.apply_flow_narrowing(idx, declared_type);
75 trace!(?result, "check_flow_usage result");
76 result
77 }
78
79 fn symbol_participates_in_flow_analysis(&self, sym_id: SymbolId) -> bool {
80 use tsz_binder::symbol_flags;
81
82 self.ctx
83 .binder
84 .get_symbol(sym_id)
85 .is_some_and(|symbol| (symbol.flags & symbol_flags::VARIABLE) != 0)
86 }
87
88 fn should_skip_flow_narrowing_for_const_literal_binding(&self, sym_id: SymbolId) -> bool {
89 let Some(symbol) = self.ctx.binder.get_symbol(sym_id) else {
90 return false;
91 };
92
93 let mut value_decl = symbol.value_declaration;
94 if value_decl.is_none() {
95 return false;
96 }
97
98 let mut decl_node = match self.ctx.arena.get(value_decl) {
99 Some(node) => node,
100 None => return false,
101 };
102
103 if decl_node.kind == SyntaxKind::Identifier as u16
106 && let Some(ext) = self.ctx.arena.get_extended(value_decl)
107 && ext.parent.is_some()
108 && let Some(parent_node) = self.ctx.arena.get(ext.parent)
109 && parent_node.kind == syntax_kind_ext::VARIABLE_DECLARATION
110 {
111 value_decl = ext.parent;
112 decl_node = parent_node;
113 }
114
115 if decl_node.kind != syntax_kind_ext::VARIABLE_DECLARATION
116 || !self.is_const_variable_declaration(value_decl)
117 {
118 return false;
119 }
120
121 let Some(var_decl) = self.ctx.arena.get_variable_declaration(decl_node) else {
122 return false;
123 };
124 if var_decl.type_annotation.is_some() || var_decl.initializer.is_none() {
125 return false;
126 }
127
128 let Some(init_node) = self.ctx.arena.get(var_decl.initializer) else {
129 return false;
130 };
131 init_node.kind == syntax_kind_ext::OBJECT_LITERAL_EXPRESSION
132 || init_node.kind == syntax_kind_ext::ARRAY_LITERAL_EXPRESSION
133 }
134
135 fn emit_definite_assignment_error(&mut self, idx: NodeIndex, sym_id: SymbolId) {
137 let Some(node) = self.ctx.arena.get(idx) else {
139 return;
142 };
143
144 let pos = node.pos;
145
146 let key = (pos, sym_id);
148 if !self.ctx.emitted_ts2454_errors.insert(key) {
149 return;
151 }
152
153 let name = self
155 .ctx
156 .binder
157 .get_symbol(sym_id)
158 .map_or_else(|| "<unknown>".to_string(), |s| s.escaped_name.clone());
159
160 let length = node.end - node.pos;
162
163 self.ctx.diagnostics.push(Diagnostic::error(
164 self.ctx.file_name.clone(),
165 pos,
166 length,
167 format!("Variable '{name}' is used before being assigned."),
168 2454, ));
170 }
171
172 pub(crate) fn is_in_default_parameter(&self, idx: NodeIndex) -> bool {
175 let mut current = idx;
176 let mut iterations = 0;
177 loop {
178 iterations += 1;
179 if iterations > MAX_TREE_WALK_ITERATIONS {
180 return false;
181 }
182 let ext = match self.ctx.arena.get_extended(current) {
183 Some(ext) => ext,
184 None => return false,
185 };
186 let parent_idx = ext.parent;
187 if parent_idx.is_none() {
188 return false;
189 }
190
191 if let Some(parent_node) = self.ctx.arena.get(parent_idx) {
193 if parent_node.kind == syntax_kind_ext::PARAMETER
194 && let Some(param) = self.ctx.arena.get_parameter(parent_node)
195 {
196 if param.initializer.is_some() {
198 let init_idx = param.initializer;
199 if self.is_node_within(idx, init_idx) {
201 return true;
202 }
203 }
204 }
205 if parent_node.is_function_like() {
207 return false;
208 }
209 }
210
211 current = parent_idx;
212 }
213 }
214
215 pub(crate) fn skip_definite_assignment_for_type(&self, declared_type: TypeId) -> bool {
222 use tsz_solver::TypeId;
223 use tsz_solver::type_contains_undefined;
224
225 if declared_type == TypeId::ANY
227 || declared_type == TypeId::UNKNOWN
228 || declared_type == TypeId::ERROR
229 {
230 return true;
231 }
232
233 type_contains_undefined(self.ctx.types, declared_type)
235 }
236
237 pub(crate) fn should_check_definite_assignment(
240 &mut self,
241 sym_id: SymbolId,
242 idx: NodeIndex,
243 ) -> bool {
244 use tsz_binder::symbol_flags;
245 use tsz_parser::parser::node::NodeAccess;
246
247 if !self.ctx.strict_null_checks() {
249 return false;
250 }
251
252 if self.is_for_in_of_initializer(idx) {
257 return false;
258 }
259
260 if self.is_destructuring_assignment_target(idx) {
264 return false;
265 }
266
267 let Some(symbol) = self.ctx.binder.get_symbol(sym_id) else {
269 return false;
270 };
271
272 if symbol.decl_file_idx != u32::MAX
275 && symbol.decl_file_idx != self.ctx.current_file_idx as u32
276 {
277 return false;
278 }
279
280 if (symbol.flags & symbol_flags::VARIABLE) == 0 {
283 return false;
284 }
285
286 let decl_id = symbol.value_declaration;
288 if decl_id.is_none() {
289 return false;
290 }
291
292 let Some(decl_node) = self.ctx.arena.get(decl_id) else {
294 return false;
295 };
296
297 let mut decl_node = decl_node;
298 let mut decl_id_to_check = decl_id;
299 if decl_node.kind == tsz_scanner::SyntaxKind::Identifier as u16
300 && let Some(info) = self.ctx.arena.node_info(decl_id)
301 && let Some(parent) = self.ctx.arena.get(info.parent)
302 {
303 decl_node = parent;
304 decl_id_to_check = info.parent;
305 }
306
307 if decl_node.kind == syntax_kind_ext::BINDING_ELEMENT {
311 let mut current = decl_id_to_check;
312 for _ in 0..10 {
313 if let Some(info) = self.ctx.arena.node_info(current) {
314 let parent = info.parent;
315 if let Some(parent_node) = self.ctx.arena.get(parent)
316 && parent_node.kind == syntax_kind_ext::PARAMETER
317 {
318 return false;
319 }
320 current = parent;
321 } else {
322 break;
323 }
324 }
325 }
326 let (has_initializer, has_exclamation) =
327 if decl_node.kind == syntax_kind_ext::VARIABLE_DECLARATION {
328 let Some(var_data) = self.ctx.arena.get_variable_declaration(decl_node) else {
329 return false;
330 };
331 (var_data.initializer.is_some(), var_data.exclamation_token)
332 } else if decl_node.kind == syntax_kind_ext::BINDING_ELEMENT {
333 let Some(_var_data) = self.ctx.arena.get_binding_element(decl_node) else {
334 return false;
335 };
336 (true, false)
337 } else {
338 return false;
339 };
340
341 if has_initializer {
347 let is_function_scoped =
348 symbol.flags & tsz_binder::symbol_flags::FUNCTION_SCOPED_VARIABLE != 0;
349
350 if let Some(usage_node) = self.ctx.arena.get(idx)
351 && should_skip_daa_for_initialized_function_scoped_var(
352 is_function_scoped,
353 self.is_source_file_global_var_decl(decl_id_to_check),
354 self.enclosing_top_level_statement_kind(decl_id_to_check),
355 usage_node.pos,
356 decl_node.end,
357 )
358 {
359 return false;
360 }
361
362 if !is_function_scoped {
363 return false;
364 }
365
366 if let Some(_usage_node) = self.ctx.arena.get(idx) {
367 if self.find_enclosing_function_or_source_file(decl_id_to_check)
375 != self.find_enclosing_function_or_source_file(idx)
376 {
377 return false;
378 }
379 }
380 }
381
382 if has_exclamation {
384 return false;
385 }
386
387 if let Some(decl_list_info) = self.ctx.arena.node_info(decl_id_to_check) {
392 let decl_list_idx = decl_list_info.parent;
393 if let Some(decl_list_node) = self.ctx.arena.get(decl_list_idx)
394 && decl_list_node.kind == syntax_kind_ext::VARIABLE_DECLARATION_LIST
395 && let Some(for_info) = self.ctx.arena.node_info(decl_list_idx)
396 {
397 let for_idx = for_info.parent;
398 if let Some(for_node) = self.ctx.arena.get(for_idx)
399 && (for_node.kind == syntax_kind_ext::FOR_IN_STATEMENT
400 || for_node.kind == syntax_kind_ext::FOR_OF_STATEMENT)
401 {
402 if let Some(usage_node) = self.ctx.arena.get(idx) {
405 if usage_node.pos >= for_node.pos {
406 return false;
407 }
408 } else {
410 return false;
411 }
412 }
413 }
414 }
415
416 if self.is_source_file_global_var_decl(decl_id_to_check)
419 && self.is_inside_function_like(idx)
420 {
421 return false;
422 }
423
424 if self.is_usage_in_nested_namespace_from_decl(decl_id_to_check, idx) {
430 return false;
431 }
432
433 if self.is_ambient_declaration(decl_id_to_check) {
435 return false;
436 }
437
438 let mut current = decl_id_to_check;
440 let mut found_container_scope = false;
441 for _ in 0..50 {
442 let Some(info) = self.ctx.arena.node_info(current) else {
443 break;
444 };
445 if let Some(node) = self.ctx.arena.get(current) {
446 if node.is_function_like() || node.kind == syntax_kind_ext::SOURCE_FILE {
448 found_container_scope = true;
449 break;
450 }
451 }
452
453 current = info.parent;
454 if current.is_none() {
455 break;
456 }
457 }
458
459 found_container_scope
461 }
462
463 fn is_source_file_global_var_decl(&self, decl_id: NodeIndex) -> bool {
464 let Some(info) = self.ctx.arena.node_info(decl_id) else {
465 return false;
466 };
467 let mut current = info.parent;
468 for _ in 0..50 {
469 let Some(node) = self.ctx.arena.get(current) else {
470 return false;
471 };
472 if node.kind == syntax_kind_ext::SOURCE_FILE {
473 return true;
474 }
475 if node.is_function_like() {
476 return false;
477 }
478 let Some(next) = self.ctx.arena.node_info(current).map(|n| n.parent) else {
479 return false;
480 };
481 current = next;
482 if current.is_none() {
483 return false;
484 }
485 }
486 false
487 }
488
489 fn enclosing_top_level_statement_kind(&self, node_idx: NodeIndex) -> Option<u16> {
490 let mut current = node_idx;
491 for _ in 0..50 {
492 let info = self.ctx.arena.node_info(current)?;
493 let parent = info.parent;
494 let parent_node = self.ctx.arena.get(parent)?;
495 if parent_node.kind == syntax_kind_ext::SOURCE_FILE {
496 return self.ctx.arena.get(current).map(|n| n.kind);
497 }
498 current = parent;
499 if current.is_none() {
500 return None;
501 }
502 }
503 None
504 }
505
506 fn is_inside_function_like(&self, idx: NodeIndex) -> bool {
507 let mut current = idx;
508 for _ in 0..50 {
509 let Some(info) = self.ctx.arena.node_info(current) else {
510 return false;
511 };
512 current = info.parent;
513 let Some(node) = self.ctx.arena.get(current) else {
514 return false;
515 };
516 if node.is_function_like() {
517 return true;
518 }
519 if node.kind == syntax_kind_ext::SOURCE_FILE {
520 return false;
521 }
522 }
523 false
524 }
525
526 fn is_usage_in_nested_namespace_from_decl(
532 &self,
533 decl_id: NodeIndex,
534 usage_idx: NodeIndex,
535 ) -> bool {
536 let Some(decl_node) = self.ctx.arena.get(decl_id) else {
537 return false;
538 };
539 let decl_pos = decl_node.pos;
540 let decl_end = decl_node.end;
541
542 let mut current = usage_idx;
543 for _ in 0..50 {
544 let Some(info) = self.ctx.arena.node_info(current) else {
545 break;
546 };
547 current = info.parent;
548 let Some(node) = self.ctx.arena.get(current) else {
549 break;
550 };
551 if node.pos <= decl_pos && node.end >= decl_end {
554 return false;
555 }
556 if node.kind == syntax_kind_ext::MODULE_DECLARATION {
559 return true;
560 }
561 if current.is_none() {
562 break;
563 }
564 }
565 false
566 }
567
568 fn is_for_in_of_initializer(&self, idx: NodeIndex) -> bool {
572 use tsz_parser::parser::node::NodeAccess;
573
574 let Some(info) = self.ctx.arena.node_info(idx) else {
575 return false;
576 };
577 let parent = info.parent;
578 let Some(parent_node) = self.ctx.arena.get(parent) else {
579 return false;
580 };
581 if (parent_node.kind == syntax_kind_ext::FOR_IN_STATEMENT
582 || parent_node.kind == syntax_kind_ext::FOR_OF_STATEMENT)
583 && let Some(for_data) = self.ctx.arena.get_for_in_of(parent_node)
584 && for_data.initializer == idx
585 {
586 return true;
587 }
588 false
589 }
590
591 fn is_destructuring_assignment_target(&self, idx: NodeIndex) -> bool {
594 let mut current = idx;
595 for _ in 0..10 {
596 let Some(info) = self.ctx.arena.node_info(current) else {
597 return false;
598 };
599 let parent = info.parent;
600 let Some(parent_node) = self.ctx.arena.get(parent) else {
601 return false;
602 };
603 match parent_node.kind {
604 k if k == syntax_kind_ext::ARRAY_LITERAL_EXPRESSION
605 || k == syntax_kind_ext::OBJECT_LITERAL_EXPRESSION
606 || k == syntax_kind_ext::SPREAD_ELEMENT
607 || k == syntax_kind_ext::SPREAD_ASSIGNMENT
608 || k == syntax_kind_ext::PROPERTY_ASSIGNMENT
609 || k == syntax_kind_ext::SHORTHAND_PROPERTY_ASSIGNMENT =>
610 {
611 current = parent;
612 }
613 k if k == syntax_kind_ext::BINARY_EXPRESSION => {
614 if let Some(bin) = self.ctx.arena.get_binary_expr(parent_node)
616 && bin.operator_token == SyntaxKind::EqualsToken as u16
617 && bin.left == current
618 {
619 return true;
620 }
621 return false;
622 }
623 k if k == syntax_kind_ext::FOR_IN_STATEMENT
624 || k == syntax_kind_ext::FOR_OF_STATEMENT =>
625 {
626 if let Some(for_node) = self.ctx.arena.get_for_in_of(parent_node)
627 && for_node.initializer == current
628 {
629 return true;
630 }
631 return false;
632 }
633 _ => return false,
634 }
635 }
636 false
637 }
638
639 pub(crate) fn is_definitely_assigned_at(&self, idx: NodeIndex) -> bool {
644 let flow_node = if let Some(flow) = self.ctx.binder.get_node_flow(idx) {
650 flow
651 } else {
652 let mut current = self.ctx.arena.get_extended(idx).map(|ext| ext.parent);
653 let mut found = None;
654 while let Some(parent) = current {
655 if parent.is_none() {
656 break;
657 }
658 if let Some(flow) = self.ctx.binder.get_node_flow(parent) {
659 found = Some(flow);
660 break;
661 }
662 current = self.ctx.arena.get_extended(parent).map(|ext| ext.parent);
663 }
664 match found {
665 Some(flow) => flow,
666 None => {
667 tracing::debug!("No flow info for {idx:?} or its ancestors");
668 return true;
669 }
670 }
671 };
672
673 let analyzer = FlowAnalyzer::with_node_types(
675 self.ctx.arena,
676 self.ctx.binder,
677 self.ctx.types,
678 &self.ctx.node_types,
679 )
680 .with_flow_cache(&self.ctx.flow_analysis_cache)
681 .with_reference_match_cache(&self.ctx.flow_reference_match_cache)
682 .with_type_environment(Rc::clone(&self.ctx.type_environment));
683
684 analyzer.is_definitely_assigned(idx, flow_node)
685 }
686
687 pub(crate) fn is_variable_used_before_declaration_in_static_block(
706 &self,
707 sym_id: SymbolId,
708 usage_idx: NodeIndex,
709 ) -> bool {
710 use tsz_binder::symbol_flags;
711
712 let Some(symbol) = self.ctx.binder.symbols.get(sym_id) else {
714 return false;
715 };
716
717 let is_block_scoped = (symbol.flags
721 & (symbol_flags::BLOCK_SCOPED_VARIABLE | symbol_flags::CLASS | symbol_flags::ENUM))
722 != 0;
723
724 if !is_block_scoped {
725 return false;
726 }
727
728 if symbol.decl_file_idx != u32::MAX
730 && symbol.decl_file_idx != self.ctx.current_file_idx as u32
731 {
732 return false;
733 }
734
735 let decl_idx = if symbol.value_declaration.is_some() {
738 symbol.value_declaration
739 } else if let Some(&first_decl) = symbol.declarations.first() {
740 first_decl
741 } else {
742 return false;
743 };
744
745 let Some(usage_node) = self.ctx.arena.get(usage_idx) else {
748 return false;
749 };
750 let Some(decl_node) = self.ctx.arena.get(decl_idx) else {
751 return false;
752 };
753
754 if usage_node.pos >= decl_node.end {
756 return false;
757 }
758
759 if symbol.flags & symbol_flags::CLASS != 0 && usage_node.pos > decl_node.pos {
764 return false;
765 }
766
767 if self.find_enclosing_static_block(usage_idx).is_some() {
772 return true;
773 }
774
775 false
776 }
777
778 pub(crate) fn is_variable_used_before_declaration_in_computed_property(
783 &self,
784 sym_id: SymbolId,
785 usage_idx: NodeIndex,
786 ) -> bool {
787 use tsz_binder::symbol_flags;
788
789 let Some(symbol) = self.ctx.binder.symbols.get(sym_id) else {
791 return false;
792 };
793
794 let is_block_scoped = (symbol.flags
796 & (symbol_flags::BLOCK_SCOPED_VARIABLE | symbol_flags::CLASS | symbol_flags::ENUM))
797 != 0;
798
799 if !is_block_scoped {
800 return false;
801 }
802
803 if symbol.decl_file_idx != u32::MAX
805 && symbol.decl_file_idx != self.ctx.current_file_idx as u32
806 {
807 return false;
808 }
809
810 let decl_idx = if symbol.value_declaration.is_some() {
812 symbol.value_declaration
813 } else if let Some(&first_decl) = symbol.declarations.first() {
814 first_decl
815 } else {
816 return false;
817 };
818
819 let Some(usage_node) = self.ctx.arena.get(usage_idx) else {
821 return false;
822 };
823 let Some(decl_node) = self.ctx.arena.get(decl_idx) else {
824 return false;
825 };
826
827 if usage_node.pos >= decl_node.end {
828 return false;
829 }
830
831 if self.find_enclosing_computed_property(usage_idx).is_some() {
833 return true;
834 }
835
836 false
837 }
838
839 pub(crate) fn is_variable_used_before_declaration_in_heritage_clause(
844 &self,
845 sym_id: SymbolId,
846 usage_idx: NodeIndex,
847 ) -> bool {
848 use tsz_binder::symbol_flags;
849
850 let Some(symbol) = self.ctx.binder.symbols.get(sym_id) else {
852 return false;
853 };
854
855 let is_block_scoped = (symbol.flags
857 & (symbol_flags::BLOCK_SCOPED_VARIABLE | symbol_flags::CLASS | symbol_flags::ENUM))
858 != 0;
859
860 if !is_block_scoped {
861 return false;
862 }
863
864 if self.is_in_type_only_context(usage_idx) {
867 return false;
868 }
869
870 if symbol.decl_file_idx != u32::MAX
873 && symbol.decl_file_idx != self.ctx.current_file_idx as u32
874 {
875 return false;
876 }
877
878 let decl_idx = if symbol.value_declaration.is_some() {
880 symbol.value_declaration
881 } else if let Some(&first_decl) = symbol.declarations.first() {
882 first_decl
883 } else {
884 return false;
885 };
886
887 let Some(usage_node) = self.ctx.arena.get(usage_idx) else {
889 return false;
890 };
891 let Some(decl_node) = self.ctx.arena.get(decl_idx) else {
892 return false;
893 };
894
895 if usage_node.pos >= decl_node.end {
896 return false;
897 }
898
899 if self.find_enclosing_heritage_clause(usage_idx).is_some() {
901 return true;
902 }
903
904 false
905 }
906
907 pub(crate) fn is_class_or_enum_used_before_declaration(
911 &self,
912 sym_id: SymbolId,
913 usage_idx: NodeIndex,
914 ) -> bool {
915 use tsz_binder::symbol_flags;
916 use tsz_parser::parser::syntax_kind_ext;
917
918 let Some(symbol) = self.ctx.binder.symbols.get(sym_id) else {
919 return false;
920 };
921
922 let is_block_scoped = (symbol.flags
924 & (symbol_flags::CLASS | symbol_flags::ENUM | symbol_flags::BLOCK_SCOPED_VARIABLE))
925 != 0;
926 if !is_block_scoped {
927 return false;
928 }
929
930 if self.is_in_type_only_context(usage_idx) {
933 return false;
934 }
935
936 if symbol.import_module.is_some() {
939 return false;
940 }
941 let is_cross_file = symbol.decl_file_idx != u32::MAX
942 && symbol.decl_file_idx != self.ctx.current_file_idx as u32;
943
944 if is_cross_file && (self.ctx.current_file_idx as u32) > symbol.decl_file_idx {
945 return false;
946 }
947
948 let is_multi_file = self.ctx.all_arenas.is_some();
955
956 let decl_idx = if symbol.value_declaration.is_some() {
958 symbol.value_declaration
959 } else if let Some(&first_decl) = symbol.declarations.first() {
960 first_decl
961 } else {
962 return false;
963 };
964
965 let Some(usage_node) = self.ctx.arena.get(usage_idx) else {
966 return false;
967 };
968
969 let mut decl_node_opt = self.ctx.arena.get(decl_idx);
970 let mut decl_arena = self.ctx.arena;
971
972 if is_cross_file
973 && let Some(arenas) = self.ctx.all_arenas.as_ref()
974 && let Some(arena) = arenas.get(symbol.decl_file_idx as usize)
975 {
976 decl_node_opt = arena.get(decl_idx);
977 decl_arena = arena.as_ref();
978 }
979
980 let Some(decl_node) = decl_node_opt else {
981 return false;
982 };
983
984 if is_multi_file && !is_cross_file {
988 let is_class = symbol.flags & symbol_flags::CLASS != 0;
989 let is_enum = symbol.flags & symbol_flags::ENUM != 0;
990 let is_var = symbol.flags & symbol_flags::BLOCK_SCOPED_VARIABLE != 0;
991 let kind_ok = (is_class
992 && (decl_node.kind == syntax_kind_ext::CLASS_DECLARATION
993 || decl_node.kind == syntax_kind_ext::CLASS_EXPRESSION))
994 || (is_enum && decl_node.kind == syntax_kind_ext::ENUM_DECLARATION)
995 || (is_var
996 && (decl_node.kind == syntax_kind_ext::VARIABLE_DECLARATION
997 || decl_node.kind == syntax_kind_ext::PARAMETER
998 || decl_node.kind == syntax_kind_ext::BINDING_ELEMENT
999 || decl_node.kind == tsz_scanner::SyntaxKind::Identifier as u16));
1000 if !kind_ok {
1001 return false;
1002 }
1003 }
1004
1005 if is_cross_file {
1009 if let Some(class) = decl_arena.get_class(decl_node)
1010 && self.has_declare_modifier_in_arena(decl_arena, &class.modifiers)
1011 {
1012 return false;
1013 }
1014 if let Some(enum_decl) = decl_arena.get_enum(decl_node)
1015 && self.has_declare_modifier_in_arena(decl_arena, &enum_decl.modifiers)
1016 {
1017 return false;
1018 }
1019 } else if self.is_ambient_declaration(decl_idx) {
1020 return false;
1021 }
1022
1023 let is_var = symbol.flags & symbol_flags::BLOCK_SCOPED_VARIABLE != 0;
1028 let mut could_be_in_initializer = false;
1029 if !is_cross_file && usage_node.pos >= decl_node.pos {
1030 if is_var && usage_node.pos <= decl_node.end {
1031 could_be_in_initializer = true;
1033 } else {
1034 return false;
1035 }
1036 }
1037
1038 let decl_container = if is_cross_file {
1041 None } else {
1043 Some(self.find_enclosing_function_or_source_file(decl_idx))
1044 };
1045
1046 let mut current = usage_idx;
1052 let mut found_decl_in_path = false;
1053 while current.is_some() {
1054 let Some(node) = self.ctx.arena.get(current) else {
1055 break;
1056 };
1057 if current == decl_idx {
1058 found_decl_in_path = true;
1059 }
1060 if Some(current) == decl_container {
1062 break;
1063 }
1064 if node.is_function_like() && !self.ctx.arena.is_immediately_invoked(current) {
1069 return false;
1070 }
1071 if node.kind == syntax_kind_ext::PROPERTY_DECLARATION
1075 && let Some(prop) = self.ctx.arena.get_property_decl(node)
1076 && !self.has_static_modifier(&prop.modifiers)
1077 {
1078 return false;
1079 }
1080 if node.kind == syntax_kind_ext::EXPORT_ASSIGNMENT {
1085 return false;
1086 }
1087 if node.kind == syntax_kind_ext::SOURCE_FILE {
1089 break;
1090 }
1091 let Some(ext) = self.ctx.arena.get_extended(current) else {
1093 break;
1094 };
1095 if ext.parent.is_none() {
1096 break;
1097 }
1098 current = ext.parent;
1099 }
1100
1101 if could_be_in_initializer && !found_decl_in_path {
1102 return false;
1105 }
1106
1107 true
1108 }
1109
1110 pub(crate) fn has_declare_modifier_in_arena(
1113 &self,
1114 arena: &tsz_parser::parser::NodeArena,
1115 modifiers: &Option<tsz_parser::parser::NodeList>,
1116 ) -> bool {
1117 arena.has_modifier(modifiers, tsz_scanner::SyntaxKind::DeclareKeyword)
1118 }
1119
1120 fn is_in_type_only_context(&self, idx: NodeIndex) -> bool {
1124 use tsz_parser::parser::syntax_kind_ext;
1125
1126 let mut current = idx;
1127 while current.is_some() {
1128 let Some(ext) = self.ctx.arena.get_extended(current) else {
1129 return false;
1130 };
1131 if ext.parent.is_none() {
1132 return false;
1133 }
1134 let Some(parent_node) = self.ctx.arena.get(ext.parent) else {
1135 return false;
1136 };
1137
1138 match parent_node.kind {
1140 syntax_kind_ext::TYPE_PREDICATE
1142 | syntax_kind_ext::TYPE_REFERENCE
1143 | syntax_kind_ext::FUNCTION_TYPE
1144 | syntax_kind_ext::CONSTRUCTOR_TYPE
1145 | syntax_kind_ext::TYPE_QUERY | syntax_kind_ext::TYPE_LITERAL
1147 | syntax_kind_ext::ARRAY_TYPE
1148 | syntax_kind_ext::TUPLE_TYPE
1149 | syntax_kind_ext::OPTIONAL_TYPE
1150 | syntax_kind_ext::REST_TYPE
1151 | syntax_kind_ext::UNION_TYPE
1152 | syntax_kind_ext::INTERSECTION_TYPE
1153 | syntax_kind_ext::CONDITIONAL_TYPE
1154 | syntax_kind_ext::INFER_TYPE
1155 | syntax_kind_ext::PARENTHESIZED_TYPE
1156 | syntax_kind_ext::THIS_TYPE
1157 | syntax_kind_ext::TYPE_OPERATOR
1158 | syntax_kind_ext::INDEXED_ACCESS_TYPE
1159 | syntax_kind_ext::MAPPED_TYPE
1160 | syntax_kind_ext::LITERAL_TYPE
1161 | syntax_kind_ext::NAMED_TUPLE_MEMBER
1162 | syntax_kind_ext::TEMPLATE_LITERAL_TYPE
1163 | syntax_kind_ext::IMPORT_TYPE
1164 | syntax_kind_ext::HERITAGE_CLAUSE
1165 | syntax_kind_ext::EXPRESSION_WITH_TYPE_ARGUMENTS => return true,
1166
1167 syntax_kind_ext::TYPE_OF_EXPRESSION | syntax_kind_ext::SOURCE_FILE => return false,
1170
1171 _ => {
1172 current = ext.parent;
1174 }
1175 }
1176 }
1177 false
1178 }
1179
1180 fn find_enclosing_function_or_source_file(&self, idx: NodeIndex) -> NodeIndex {
1182 use tsz_parser::parser::syntax_kind_ext;
1183
1184 let mut current = idx;
1185 while current.is_some() {
1186 let Some(node) = self.ctx.arena.get(current) else {
1187 break;
1188 };
1189 if node.is_function_like() || node.kind == syntax_kind_ext::SOURCE_FILE {
1190 return current;
1191 }
1192 let Some(ext) = self.ctx.arena.get_extended(current) else {
1193 break;
1194 };
1195 if ext.parent.is_none() {
1196 break;
1197 }
1198 current = ext.parent;
1199 }
1200 current
1201 }
1202}
1203
1204const fn is_unconditional_top_level_statement(kind: u16) -> bool {
1205 kind == syntax_kind_ext::VARIABLE_STATEMENT || kind == syntax_kind_ext::FOR_STATEMENT
1206}
1207
1208fn should_skip_daa_for_initialized_function_scoped_var(
1209 is_function_scoped: bool,
1210 is_source_file_global: bool,
1211 top_level_statement_kind: Option<u16>,
1212 usage_pos: u32,
1213 declaration_end: u32,
1214) -> bool {
1215 is_function_scoped
1216 && is_source_file_global
1217 && usage_pos >= declaration_end
1218 && top_level_statement_kind.is_some_and(is_unconditional_top_level_statement)
1219}
1220
1221#[cfg(test)]
1222mod tests {
1223 use super::{should_skip_daa_for_initialized_function_scoped_var, syntax_kind_ext};
1224
1225 #[test]
1226 fn skips_after_top_level_var_initializer_runs() {
1227 assert!(should_skip_daa_for_initialized_function_scoped_var(
1228 true,
1229 true,
1230 Some(syntax_kind_ext::VARIABLE_STATEMENT),
1231 100,
1232 50
1233 ));
1234 }
1235
1236 #[test]
1237 fn skips_after_top_level_for_initializer_runs() {
1238 assert!(should_skip_daa_for_initialized_function_scoped_var(
1239 true,
1240 true,
1241 Some(syntax_kind_ext::FOR_STATEMENT),
1242 200,
1243 80
1244 ));
1245 }
1246
1247 #[test]
1248 fn does_not_skip_when_declaration_is_conditional() {
1249 assert!(!should_skip_daa_for_initialized_function_scoped_var(
1250 true,
1251 true,
1252 Some(syntax_kind_ext::IF_STATEMENT),
1253 120,
1254 40
1255 ));
1256 }
1257
1258 #[test]
1259 fn does_not_skip_when_usage_precedes_declaration_end() {
1260 assert!(!should_skip_daa_for_initialized_function_scoped_var(
1261 true,
1262 true,
1263 Some(syntax_kind_ext::VARIABLE_STATEMENT),
1264 30,
1265 40
1266 ));
1267 }
1268}