@@ -5110,7 +5110,10 @@ impl<'a> ThinCheckerState<'a> {
51105110 return TypeId::ERROR;
51115111 }
51125112 let declared_type = self.get_type_of_symbol(sym_id);
5113- if self.should_check_definite_assignment(sym_id, idx)
5113+ // Check for static block TDZ violation (variable used before declaration in source order)
5114+ if self.is_variable_used_before_declaration_in_static_block(sym_id, idx) {
5115+ self.error_variable_used_before_assigned_at(name, idx);
5116+ } else if self.should_check_definite_assignment(sym_id, idx)
51145117 && !self.is_definitely_assigned_at(idx)
51155118 {
51165119 self.error_variable_used_before_assigned_at(name, idx);
@@ -5613,6 +5616,119 @@ impl<'a> ThinCheckerState<'a> {
56135616 }
56145617 }
56155618
5619+ /// Find the enclosing static block for a node, if any.
5620+ ///
5621+ /// Returns the NodeIndex of the CLASS_STATIC_BLOCK_DECLARATION if the node is inside one.
5622+ fn find_enclosing_static_block(&self, idx: NodeIndex) -> Option<NodeIndex> {
5623+ let mut current = idx;
5624+ while !current.is_none() {
5625+ if let Some(node) = self.ctx.arena.get(current) {
5626+ if node.kind == syntax_kind_ext::CLASS_STATIC_BLOCK_DECLARATION {
5627+ return Some(current);
5628+ }
5629+ // Stop at function boundaries (don't consider outer static blocks)
5630+ if node.kind == syntax_kind_ext::FUNCTION_DECLARATION
5631+ || node.kind == syntax_kind_ext::FUNCTION_EXPRESSION
5632+ || node.kind == syntax_kind_ext::ARROW_FUNCTION
5633+ || node.kind == syntax_kind_ext::METHOD_DECLARATION
5634+ || node.kind == syntax_kind_ext::CONSTRUCTOR
5635+ {
5636+ return None;
5637+ }
5638+ }
5639+ let ext = self.ctx.arena.get_extended(current)?;
5640+ if ext.parent.is_none() {
5641+ return None;
5642+ }
5643+ current = ext.parent;
5644+ }
5645+ None
5646+ }
5647+
5648+ /// Find the class declaration containing a static block.
5649+ ///
5650+ /// Given a static block node, returns the parent CLASS_DECLARATION or CLASS_EXPRESSION.
5651+ fn find_class_for_static_block(&self, static_block_idx: NodeIndex) -> Option<NodeIndex> {
5652+ let ext = self.ctx.arena.get_extended(static_block_idx)?;
5653+ let parent = ext.parent;
5654+ if parent.is_none() {
5655+ return None;
5656+ }
5657+ let parent_node = self.ctx.arena.get(parent)?;
5658+ if parent_node.kind == syntax_kind_ext::CLASS_DECLARATION
5659+ || parent_node.kind == syntax_kind_ext::CLASS_EXPRESSION
5660+ {
5661+ Some(parent)
5662+ } else {
5663+ None
5664+ }
5665+ }
5666+
5667+ /// Check if a variable is used in a static block before its declaration (TDZ check).
5668+ ///
5669+ /// In TypeScript, if a variable is declared at module level AFTER a class declaration,
5670+ /// using that variable inside the class's static block should emit TS2454.
5671+ ///
5672+ /// Example:
5673+ /// ```typescript
5674+ /// class Baz {
5675+ /// static {
5676+ /// console.log(FOO); // Error: Variable 'FOO' is used before being assigned
5677+ /// }
5678+ /// }
5679+ /// const FOO = "FOO"; // Declared after the class
5680+ /// ```
5681+ fn is_variable_used_before_declaration_in_static_block(
5682+ &self,
5683+ sym_id: SymbolId,
5684+ usage_idx: NodeIndex,
5685+ ) -> bool {
5686+ // Check if we're inside a static block
5687+ let Some(static_block_idx) = self.find_enclosing_static_block(usage_idx) else {
5688+ return false;
5689+ };
5690+
5691+ // Get the class containing the static block
5692+ let Some(class_idx) = self.find_class_for_static_block(static_block_idx) else {
5693+ return false;
5694+ };
5695+
5696+ // Get the class position
5697+ let Some(class_node) = self.ctx.arena.get(class_idx) else {
5698+ return false;
5699+ };
5700+ let class_pos = class_node.pos;
5701+
5702+ // Get the symbol's declaration
5703+ let Some(symbol) = self.ctx.binder.get_symbol(sym_id) else {
5704+ return false;
5705+ };
5706+
5707+ // Check if the symbol is a module-level variable (not a class member)
5708+ // We're looking for variables declared outside the class
5709+ if (symbol.flags & symbol_flags::VARIABLE) == 0 {
5710+ return false;
5711+ }
5712+
5713+ // Get the position of the variable's declaration
5714+ for &decl_idx in &symbol.declarations {
5715+ // Check if this is a variable declaration
5716+ let Some(var_stmt_idx) = self.find_enclosing_variable_statement(decl_idx) else {
5717+ continue;
5718+ };
5719+ let Some(var_stmt_node) = self.ctx.arena.get(var_stmt_idx) else {
5720+ continue;
5721+ };
5722+
5723+ // Variable is declared AFTER the class - this is TDZ error
5724+ if var_stmt_node.pos > class_pos {
5725+ return true;
5726+ }
5727+ }
5728+
5729+ false
5730+ }
5731+
56165732 /// Get type of a symbol.
56175733 pub fn get_type_of_symbol(&mut self, sym_id: SymbolId) -> TypeId {
56185734 use crate::solver::SymbolRef;
0 commit comments