1use std::collections::HashMap;
2use std::collections::HashSet;
3
4use react_compiler_diagnostics::CompilerDiagnostic;
5use react_compiler_diagnostics::CompilerError;
6use react_compiler_diagnostics::CompilerErrorDetail;
7use react_compiler_diagnostics::ErrorCategory;
8
9use crate::default_module_type_provider::default_module_type_provider;
10use crate::environment_config::EnvironmentConfig;
11use crate::globals::Global;
12use crate::globals::GlobalRegistry;
13use crate::globals::{self};
14use crate::object_shape::BUILT_IN_MIXED_READONLY_ID;
15use crate::object_shape::FunctionSignature;
16use crate::object_shape::HookKind;
17use crate::object_shape::HookSignatureBuilder;
18use crate::object_shape::ShapeRegistry;
19use crate::object_shape::add_hook;
20use crate::object_shape::default_mutating_hook;
21use crate::object_shape::default_nonmutating_hook;
22use crate::*;
23
24#[derive(Debug, Clone)]
27pub struct BindingRename {
28 pub original: String,
29 pub renamed: String,
30 pub declaration_start: u32,
31}
32
33#[derive(Debug, Clone, Copy, PartialEq, Eq)]
36pub enum OutputMode {
37 Ssr,
38 Client,
39 Lint,
40}
41
42pub struct Environment {
43 pub next_block_id_counter: u32,
45 pub next_scope_id_counter: u32,
46 next_mutable_range_id_counter: u32,
47
48 pub identifiers: Vec<Identifier>,
50 pub types: Vec<Type>,
51 pub scopes: Vec<ReactiveScope>,
52 pub functions: Vec<HirFunction>,
53
54 pub errors: CompilerError,
56
57 pub fn_type: ReactFunctionType,
59
60 pub output_mode: OutputMode,
62
63 pub code: Option<String>,
65
66 pub filename: Option<String>,
68
69 pub instrument_fn_name: Option<String>,
72 pub instrument_gating_name: Option<String>,
73 pub hook_guard_name: Option<String>,
74
75 pub renames: Vec<BindingRename>,
78
79 pub reference_node_ids: HashSet<u32>,
84
85 hoisted_identifiers: HashSet<u32>,
89
90 pub validate_preserve_existing_memoization_guarantees: bool,
92 pub validate_no_set_state_in_render: bool,
93 pub enable_preserve_existing_memoization_guarantees: bool,
94
95 globals: GlobalRegistry,
97 pub shapes: ShapeRegistry,
98 module_types: HashMap<String, Option<Global>>,
99 module_type_errors: HashMap<String, Vec<String>>,
100
101 pub config: EnvironmentConfig,
103
104 default_nonmutating_hook: Option<Global>,
106 default_mutating_hook: Option<Global>,
107
108 outlined_functions: Vec<OutlinedFunctionEntry>,
110
111 uid_known_names: Option<HashSet<String>>,
115}
116
117#[derive(Debug, Clone)]
120pub struct OutlinedFunctionEntry {
121 pub func: HirFunction,
122 pub fn_type: Option<ReactFunctionType>,
123}
124
125impl Environment {
126 pub fn new() -> Self {
127 Self::with_config(EnvironmentConfig::default())
128 }
129
130 pub fn with_config(config: EnvironmentConfig) -> Self {
135 let mut shapes = ShapeRegistry::with_base(globals::base_shapes());
136 let mut global_registry = GlobalRegistry::with_base(globals::base_globals());
137
138 for (hook_name, hook) in &config.custom_hooks {
140 if global_registry.contains_key(hook_name) {
142 continue;
143 }
144 let return_type = if hook.transitive_mixed_data {
145 Type::Object {
146 shape_id: Some(BUILT_IN_MIXED_READONLY_ID.to_string()),
147 }
148 } else {
149 Type::Poly
150 };
151 let hook_type = add_hook(
152 &mut shapes,
153 HookSignatureBuilder {
154 rest_param: Some(hook.effect_kind),
155 return_type,
156 return_value_kind: hook.value_kind,
157 hook_kind: HookKind::Custom,
158 no_alias: hook.no_alias,
159 ..Default::default()
160 },
161 None,
162 );
163 global_registry.insert(hook_name.clone(), hook_type);
164 }
165
166 let mut module_types: HashMap<String, Option<Global>> = HashMap::new();
168 if config.enable_custom_type_definition_for_reanimated {
169 let reanimated_module_type = globals::get_reanimated_module_type(&mut shapes);
170 module_types.insert(
171 "react-native-reanimated".to_string(),
172 Some(reanimated_module_type),
173 );
174 }
175
176 Self {
177 next_block_id_counter: 0,
178 next_scope_id_counter: 0,
179 next_mutable_range_id_counter: 0,
180 identifiers: Vec::new(),
181 types: Vec::new(),
182 scopes: Vec::new(),
183 functions: Vec::new(),
184 errors: CompilerError::new(),
185 fn_type: ReactFunctionType::Other,
186 output_mode: OutputMode::Client,
187 code: None,
188 filename: None,
189 instrument_fn_name: None,
190 instrument_gating_name: None,
191 hook_guard_name: None,
192 renames: Vec::new(),
193 reference_node_ids: HashSet::new(),
194 hoisted_identifiers: HashSet::new(),
195 validate_preserve_existing_memoization_guarantees: config
196 .validate_preserve_existing_memoization_guarantees,
197 validate_no_set_state_in_render: config.validate_no_set_state_in_render,
198 enable_preserve_existing_memoization_guarantees: config
199 .enable_preserve_existing_memoization_guarantees,
200 globals: global_registry,
201 shapes,
202 module_types,
203 module_type_errors: HashMap::new(),
204 default_nonmutating_hook: None,
205 default_mutating_hook: None,
206 outlined_functions: Vec::new(),
207 uid_known_names: None,
208 config,
209 }
210 }
211
212 pub fn for_outlined_fn(&self, fn_type: ReactFunctionType) -> Self {
219 Self {
220 next_block_id_counter: self.next_block_id_counter,
224 next_scope_id_counter: self.scopes.len() as u32,
226 next_mutable_range_id_counter: self.next_mutable_range_id_counter,
227 identifiers: self.identifiers.clone(),
228 types: self.types.clone(),
229 scopes: self.scopes.clone(),
230 functions: self.functions.clone(),
231 errors: CompilerError::new(),
232 fn_type,
233 output_mode: self.output_mode,
234 code: self.code.clone(),
235 filename: self.filename.clone(),
236 instrument_fn_name: self.instrument_fn_name.clone(),
237 instrument_gating_name: self.instrument_gating_name.clone(),
238 hook_guard_name: self.hook_guard_name.clone(),
239 renames: Vec::new(),
240 reference_node_ids: HashSet::new(),
241 hoisted_identifiers: HashSet::new(),
242 validate_preserve_existing_memoization_guarantees: self
243 .validate_preserve_existing_memoization_guarantees,
244 validate_no_set_state_in_render: self.validate_no_set_state_in_render,
245 enable_preserve_existing_memoization_guarantees: self
246 .enable_preserve_existing_memoization_guarantees,
247 globals: self.globals.clone(),
248 shapes: self.shapes.clone(),
249 module_types: self.module_types.clone(),
250 module_type_errors: self.module_type_errors.clone(),
251 config: self.config.clone(),
252 default_nonmutating_hook: self.default_nonmutating_hook.clone(),
253 default_mutating_hook: self.default_mutating_hook.clone(),
254 outlined_functions: Vec::new(),
255 uid_known_names: self.uid_known_names.clone(),
256 }
257 }
258
259 pub fn next_block_id(&mut self) -> BlockId {
260 let id = BlockId(self.next_block_id_counter);
261 self.next_block_id_counter += 1;
262 id
263 }
264
265 pub fn new_mutable_range(&mut self, start: EvaluationOrder, end: EvaluationOrder) -> MutableRange {
269 let id = MutableRangeId(self.next_mutable_range_id_counter);
270 self.next_mutable_range_id_counter += 1;
271 MutableRange { id, start, end }
272 }
273
274 pub fn next_identifier_id(&mut self) -> IdentifierId {
277 let id = IdentifierId(self.identifiers.len() as u32);
278 let type_id = self.make_type();
279 let mutable_range = self.new_mutable_range(EvaluationOrder(0), EvaluationOrder(0));
280 self.identifiers.push(Identifier {
281 id,
282 declaration_id: DeclarationId(id.0),
283 name: None,
284 mutable_range,
285 scope: None,
286 type_: type_id,
287 loc: None,
288 });
289 id
290 }
291
292 pub fn next_scope_id(&mut self) -> ScopeId {
294 let id = ScopeId(self.next_scope_id_counter);
295 self.next_scope_id_counter += 1;
296 let range = self.new_mutable_range(EvaluationOrder(0), EvaluationOrder(0));
297 self.scopes.push(ReactiveScope {
298 id,
299 range,
300 dependencies: Vec::new(),
301 declarations: Vec::new(),
302 reassignments: Vec::new(),
303 early_return_value: None,
304 merged: Vec::new(),
305 loc: None,
306 });
307 id
308 }
309
310 pub fn next_type_id(&mut self) -> TypeId {
312 let id = TypeId(self.types.len() as u32);
313 self.types.push(Type::TypeVar { id });
314 id
315 }
316
317 pub fn make_type(&mut self) -> TypeId {
319 self.next_type_id()
320 }
321
322 pub fn add_function(&mut self, func: HirFunction) -> FunctionId {
323 let id = FunctionId(self.functions.len() as u32);
324 self.functions.push(func);
325 id
326 }
327
328 pub fn record_error(&mut self, detail: CompilerErrorDetail) -> Result<(), CompilerError> {
329 if detail.category == ErrorCategory::Invariant {
330 let detail_clone = detail.clone();
331 self.errors.push_error_detail(detail);
332 let mut err = CompilerError::new();
333 err.push_error_detail(detail_clone);
334 return Err(err);
335 }
336 self.errors.push_error_detail(detail);
337 Ok(())
338 }
339
340 pub fn record_diagnostic(&mut self, diagnostic: CompilerDiagnostic) {
341 self.errors.push_diagnostic(diagnostic);
342 }
343
344 pub fn has_errors(&self) -> bool {
345 self.errors.has_any_errors()
346 }
347
348 pub fn error_count(&self) -> usize {
349 self.errors.details.len()
350 }
351
352 pub fn has_invariant_errors(&self) -> bool {
356 self.errors.has_invariant_errors()
357 }
358
359 pub fn errors(&self) -> &CompilerError {
360 &self.errors
361 }
362
363 pub fn take_errors(&mut self) -> CompilerError {
364 let mut errors = std::mem::take(&mut self.errors);
365 errors.is_thrown = false;
368 errors
369 }
370
371 pub fn take_errors_since(&mut self, since_count: usize) -> CompilerError {
374 let mut taken = CompilerError::new();
375 if self.errors.details.len() > since_count {
376 taken.details = self.errors.details.split_off(since_count);
377 }
378 taken
379 }
380
381 pub fn take_invariant_errors(&mut self) -> CompilerError {
385 let mut invariant = CompilerError::new();
386 let mut remaining = CompilerError::new();
387 let old = std::mem::take(&mut self.errors);
388 for detail in old.details {
389 let is_invariant = match &detail {
390 react_compiler_diagnostics::CompilerErrorOrDiagnostic::Diagnostic(d) => {
391 d.category == react_compiler_diagnostics::ErrorCategory::Invariant
392 }
393 react_compiler_diagnostics::CompilerErrorOrDiagnostic::ErrorDetail(d) => {
394 d.category == react_compiler_diagnostics::ErrorCategory::Invariant
395 }
396 };
397 if is_invariant {
398 invariant.details.push(detail);
399 } else {
400 remaining.details.push(detail);
401 }
402 }
403 self.errors = remaining;
404 invariant
405 }
406
407 pub fn has_todo_errors(&self) -> bool {
410 self.errors.details.iter().any(|d| match d {
411 react_compiler_diagnostics::CompilerErrorOrDiagnostic::Diagnostic(d) => {
412 d.category == react_compiler_diagnostics::ErrorCategory::Todo
413 }
414 react_compiler_diagnostics::CompilerErrorOrDiagnostic::ErrorDetail(d) => {
415 d.category == react_compiler_diagnostics::ErrorCategory::Todo
416 }
417 })
418 }
419
420 pub fn take_thrown_errors(&mut self) -> CompilerError {
423 let mut thrown = CompilerError::new();
424 let mut remaining = CompilerError::new();
425 let old = std::mem::take(&mut self.errors);
426 for detail in old.details {
427 let is_thrown = match &detail {
428 react_compiler_diagnostics::CompilerErrorOrDiagnostic::Diagnostic(d) => {
429 d.category == react_compiler_diagnostics::ErrorCategory::Invariant
430 || d.category == react_compiler_diagnostics::ErrorCategory::Todo
431 }
432 react_compiler_diagnostics::CompilerErrorOrDiagnostic::ErrorDetail(d) => {
433 d.category == react_compiler_diagnostics::ErrorCategory::Invariant
434 || d.category == react_compiler_diagnostics::ErrorCategory::Todo
435 }
436 };
437 if is_thrown {
438 thrown.details.push(detail);
439 } else {
440 remaining.details.push(detail);
441 }
442 }
443 self.errors = remaining;
444 thrown
445 }
446
447 pub fn is_hoisted_identifier(&self, binding_id: u32) -> bool {
449 self.hoisted_identifiers.contains(&binding_id)
450 }
451
452 pub fn add_hoisted_identifier(&mut self, binding_id: u32) {
454 self.hoisted_identifiers.insert(binding_id);
455 }
456
457 pub fn get_global_declaration(
466 &mut self,
467 binding: &NonLocalBinding,
468 loc: Option<SourceLocation>,
469 ) -> Result<Option<Global>, CompilerError> {
470 match binding {
471 NonLocalBinding::ModuleLocal { name, .. } => {
472 if is_hook_name(name) {
473 Ok(Some(self.get_custom_hook_type()))
474 } else {
475 Ok(None)
476 }
477 }
478 NonLocalBinding::Global { name, .. } => {
479 if let Some(ty) = self.globals.get(name) {
480 return Ok(Some(ty.clone()));
481 }
482 if is_hook_name(name) {
483 Ok(Some(self.get_custom_hook_type()))
484 } else {
485 Ok(None)
486 }
487 }
488 NonLocalBinding::ImportSpecifier {
489 name,
490 module,
491 imported,
492 } => {
493 if self.is_known_react_module(module) {
494 if let Some(ty) = self.globals.get(imported) {
495 return Ok(Some(ty.clone()));
496 }
497 if is_hook_name(imported) || is_hook_name(name) {
498 return Ok(Some(self.get_custom_hook_type()));
499 }
500 return Ok(None);
501 }
502
503 let module_type = self.resolve_module_type(module);
506
507 if let Some(errors) = self.module_type_errors.remove(module.as_str()) {
509 if let Some(first_error) = errors.into_iter().next() {
510 self.record_error(
511 CompilerErrorDetail::new(
512 ErrorCategory::Config,
513 "Invalid type configuration for module",
514 )
515 .with_description(format!("{}", first_error))
516 .with_loc(loc),
517 )?;
518 }
519 }
520
521 if let Some(module_type) = module_type {
522 if let Some(imported_type) =
523 Self::get_property_type_from_shapes(&self.shapes, &module_type, imported)
524 {
525 return Ok(Some(imported_type));
526 }
527 }
528
529 if is_hook_name(imported) || is_hook_name(name) {
530 Ok(Some(self.get_custom_hook_type()))
531 } else {
532 Ok(None)
533 }
534 }
535 NonLocalBinding::ImportDefault { name, module }
536 | NonLocalBinding::ImportNamespace { name, module } => {
537 let is_default = matches!(binding, NonLocalBinding::ImportDefault { .. });
538
539 if self.is_known_react_module(module) {
540 if let Some(ty) = self.globals.get(name) {
541 return Ok(Some(ty.clone()));
542 }
543 if is_hook_name(name) {
544 return Ok(Some(self.get_custom_hook_type()));
545 }
546 return Ok(None);
547 }
548
549 let module_type = self.resolve_module_type(module);
550
551 if let Some(errors) = self.module_type_errors.remove(module.as_str()) {
553 if let Some(first_error) = errors.into_iter().next() {
554 self.record_error(
555 CompilerErrorDetail::new(
556 ErrorCategory::Config,
557 "Invalid type configuration for module",
558 )
559 .with_description(format!("{}", first_error))
560 .with_loc(loc),
561 )?;
562 }
563 }
564
565 if let Some(module_type) = module_type {
566 let imported_type = if is_default {
567 Self::get_property_type_from_shapes(&self.shapes, &module_type, "default")
568 } else {
569 Some(module_type)
570 };
571 if let Some(imported_type) = imported_type {
572 let expect_hook = is_hook_name(module);
574 let is_hook = self
575 .get_hook_kind_for_type(&imported_type)
576 .ok()
577 .flatten()
578 .is_some();
579 if expect_hook != is_hook {
580 self.record_error(
581 CompilerErrorDetail::new(
582 ErrorCategory::Config,
583 "Invalid type configuration for module",
584 )
585 .with_description(format!(
586 "Expected type for `import ... from '{}'` {} based on the module name",
587 module,
588 if expect_hook { "to be a hook" } else { "not to be a hook" }
589 ))
590 .with_loc(loc),
591 )?;
592 }
593 return Ok(Some(imported_type));
594 }
595 }
596
597 if is_hook_name(name) {
598 Ok(Some(self.get_custom_hook_type()))
599 } else {
600 Ok(None)
601 }
602 }
603 }
604 }
605
606 fn get_property_type_from_shapes(
610 shapes: &ShapeRegistry,
611 receiver: &Type,
612 property: &str,
613 ) -> Option<Type> {
614 let shape_id = match receiver {
615 Type::Object { shape_id } | Type::Function { shape_id, .. } => shape_id.as_deref(),
616 _ => None,
617 };
618 if let Some(shape_id) = shape_id {
619 let shape = shapes.get(shape_id)?;
620 if let Some(ty) = shape.properties.get(property) {
621 return Some(ty.clone());
622 }
623 if let Some(ty) = shape.properties.get("*") {
624 return Some(ty.clone());
625 }
626 }
630 None
631 }
632
633 pub fn get_property_type(
636 &mut self,
637 receiver: &Type,
638 property: &str,
639 ) -> Result<Option<Type>, CompilerDiagnostic> {
640 let shape_id = match receiver {
641 Type::Object { shape_id } | Type::Function { shape_id, .. } => shape_id.as_deref(),
642 _ => None,
643 };
644 if let Some(shape_id) = shape_id {
645 let shape = self.shapes.get(shape_id).ok_or_else(|| {
646 CompilerDiagnostic::new(
647 ErrorCategory::Invariant,
648 format!(
649 "[HIR] Forget internal error: cannot resolve shape {}",
650 shape_id
651 ),
652 None,
653 )
654 })?;
655 if let Some(ty) = shape.properties.get(property) {
656 return Ok(Some(ty.clone()));
657 }
658 if let Some(ty) = shape.properties.get("*") {
660 return Ok(Some(ty.clone()));
661 }
662 if is_hook_name(property) {
664 return Ok(Some(self.get_custom_hook_type()));
665 }
666 return Ok(None);
667 }
668 if is_hook_name(property) {
670 return Ok(Some(self.get_custom_hook_type()));
671 }
672 Ok(None)
673 }
674
675 pub fn get_property_type_numeric(
678 &self,
679 receiver: &Type,
680 ) -> Result<Option<Type>, CompilerDiagnostic> {
681 let shape_id = match receiver {
682 Type::Object { shape_id } | Type::Function { shape_id, .. } => shape_id.as_deref(),
683 _ => None,
684 };
685 if let Some(shape_id) = shape_id {
686 let shape = self.shapes.get(shape_id).ok_or_else(|| {
687 CompilerDiagnostic::new(
688 ErrorCategory::Invariant,
689 format!(
690 "[HIR] Forget internal error: cannot resolve shape {}",
691 shape_id
692 ),
693 None,
694 )
695 })?;
696 return Ok(shape.properties.get("*").cloned());
697 }
698 Ok(None)
699 }
700
701 pub fn get_fallthrough_property_type(
704 &self,
705 receiver: &Type,
706 ) -> Result<Option<Type>, CompilerDiagnostic> {
707 let shape_id = match receiver {
708 Type::Object { shape_id } | Type::Function { shape_id, .. } => shape_id.as_deref(),
709 _ => None,
710 };
711 if let Some(shape_id) = shape_id {
712 let shape = self.shapes.get(shape_id).ok_or_else(|| {
713 CompilerDiagnostic::new(
714 ErrorCategory::Invariant,
715 format!(
716 "[HIR] Forget internal error: cannot resolve shape {}",
717 shape_id
718 ),
719 None,
720 )
721 })?;
722 return Ok(shape.properties.get("*").cloned());
723 }
724 Ok(None)
725 }
726
727 pub fn get_function_signature(
730 &self,
731 ty: &Type,
732 ) -> Result<Option<&FunctionSignature>, CompilerDiagnostic> {
733 let shape_id = match ty {
734 Type::Function { shape_id, .. } => shape_id.as_deref(),
735 _ => return Ok(None),
736 };
737 if let Some(shape_id) = shape_id {
738 let shape = self.shapes.get(shape_id).ok_or_else(|| {
739 CompilerDiagnostic::new(
740 ErrorCategory::Invariant,
741 format!(
742 "[HIR] Forget internal error: cannot resolve shape {}",
743 shape_id
744 ),
745 None,
746 )
747 })?;
748 return Ok(shape.function_type.as_ref());
749 }
750 Ok(None)
751 }
752
753 pub fn get_hook_kind_for_type(
756 &self,
757 ty: &Type,
758 ) -> Result<Option<&HookKind>, CompilerDiagnostic> {
759 Ok(self
760 .get_function_signature(ty)?
761 .and_then(|sig| sig.hook_kind.as_ref()))
762 }
763
764 fn resolve_module_type(&mut self, module_name: &str) -> Option<Global> {
768 if let Some(cached) = self.module_types.get(module_name) {
769 return cached.clone();
770 }
771
772 let module_config = self
774 .config
775 .module_type_provider
776 .as_ref()
777 .and_then(|map| map.get(module_name).cloned())
778 .or_else(|| default_module_type_provider(module_name));
779
780 let module_type = module_config.map(|config| {
781 let mut type_errors: Vec<String> = Vec::new();
782 let ty = globals::install_type_config_with_errors(
783 &mut self.globals,
784 &mut self.shapes,
785 &config,
786 module_name,
787 (),
788 &mut type_errors,
789 );
790 for err in type_errors {
792 self.module_type_errors
793 .entry(module_name.to_string())
794 .or_default()
795 .push(err);
796 }
797 ty
798 });
799 self.module_types
800 .insert(module_name.to_string(), module_type.clone());
801 module_type
802 }
803
804 fn is_known_react_module(&self, module_name: &str) -> bool {
805 let lower = module_name.to_lowercase();
806 lower == "react" || lower == "react-dom"
807 }
808
809 fn get_custom_hook_type(&mut self) -> Global {
810 if self.config.enable_assume_hooks_follow_rules_of_react {
811 if self.default_nonmutating_hook.is_none() {
812 self.default_nonmutating_hook = Some(default_nonmutating_hook(&mut self.shapes));
813 }
814 self.default_nonmutating_hook.clone().unwrap()
815 } else {
816 if self.default_mutating_hook.is_none() {
817 self.default_mutating_hook = Some(default_mutating_hook(&mut self.shapes));
818 }
819 self.default_mutating_hook.clone().unwrap()
820 }
821 }
822
823 pub fn get_custom_hook_type_opt(&mut self) -> Option<Global> {
826 Some(self.get_custom_hook_type())
827 }
828
829 pub fn shapes(&self) -> &ShapeRegistry {
831 &self.shapes
832 }
833
834 pub fn globals(&self) -> &GlobalRegistry {
836 &self.globals
837 }
838
839 pub fn generate_globally_unique_identifier_name(&mut self, name: Option<&str>) -> String {
849 let base = name.unwrap_or("temp");
850 let mut dashed = String::new();
855 for c in base.chars() {
856 if c.is_ascii_alphanumeric() || c == '_' || c == '$' {
857 dashed.push(c);
858 } else {
859 dashed.push('-');
860 }
861 }
862 let trimmed = dashed.trim_start_matches(|c: char| c == '-' || c.is_ascii_digit());
864 let mut camel = String::new();
866 let mut chars = trimmed.chars().peekable();
867 while let Some(c) = chars.next() {
868 if c == '-' {
869 while chars.peek() == Some(&'-') {
870 chars.next();
871 }
872 if let Some(next) = chars.next() {
873 for uc in next.to_uppercase() {
874 camel.push(uc);
875 }
876 }
877 } else {
878 camel.push(c);
879 }
880 }
881 if camel.is_empty() {
882 camel = "temp".to_string();
883 }
884 let stripped = camel.trim_start_matches('_');
886 let stripped = stripped.trim_end_matches(|c: char| c.is_ascii_digit());
887 let uid_base = if stripped.is_empty() {
888 "temp"
889 } else {
890 stripped
891 };
892
893 if self.uid_known_names.is_none() {
896 let mut known = HashSet::new();
897 for id in &self.identifiers {
898 if let Some(name) = &id.name {
899 known.insert(name.value().to_string());
900 }
901 }
902 self.uid_known_names = Some(known);
903 }
904
905 let mut i = 1u32;
907 let uid = loop {
908 let candidate = if i == 1 {
909 format!("_{}", uid_base)
910 } else {
911 format!("_{}{}", uid_base, i)
912 };
913 i += 1;
914 if !self.uid_known_names.as_ref().unwrap().contains(&candidate) {
915 break candidate;
916 }
917 };
918
919 self.uid_known_names.as_mut().unwrap().insert(uid.clone());
921
922 uid
923 }
924
925 pub fn seed_uid_known_names(&mut self, names: &HashSet<String>) {
929 match &mut self.uid_known_names {
930 Some(existing) => existing.extend(names.iter().cloned()),
931 None => self.uid_known_names = Some(names.clone()),
932 }
933 }
934
935 pub fn take_uid_known_names(&mut self) -> Option<HashSet<String>> {
937 self.uid_known_names.take()
938 }
939
940 pub fn outline_function(&mut self, func: HirFunction, fn_type: Option<ReactFunctionType>) {
943 self.outlined_functions
944 .push(OutlinedFunctionEntry { func, fn_type });
945 }
946
947 pub fn get_outlined_functions(&self) -> &[OutlinedFunctionEntry] {
949 &self.outlined_functions
950 }
951
952 pub fn take_outlined_functions(&mut self) -> Vec<OutlinedFunctionEntry> {
954 std::mem::take(&mut self.outlined_functions)
955 }
956
957 pub fn enable_memoization(&self) -> bool {
961 match self.output_mode {
962 OutputMode::Client | OutputMode::Lint => true,
963 OutputMode::Ssr => false,
964 }
965 }
966
967 pub fn enable_validations(&self) -> bool {
970 match self.output_mode {
971 OutputMode::Client | OutputMode::Lint | OutputMode::Ssr => true,
972 }
973 }
974
975 pub fn identifier_name_for_id(&self, id: IdentifierId) -> Option<String> {
989 let ident = &self.identifiers[id.0 as usize];
990 if let Some(name) = &ident.name {
991 return Some(name.value().to_string());
992 }
993 let decl_id = ident.declaration_id;
995 for other in &self.identifiers {
996 if other.declaration_id == decl_id {
997 if let Some(IdentifierName::Named(name)) = &other.name {
998 return Some(name.clone());
999 }
1000 }
1001 }
1002 None
1003 }
1004
1005 pub fn has_no_alias_signature(&self, identifier_id: IdentifierId) -> bool {
1012 let ty = &self.types[self.identifiers[identifier_id.0 as usize].type_.0 as usize];
1013 self.get_function_signature(ty)
1014 .ok()
1015 .flatten()
1016 .map_or(false, |sig| sig.no_alias)
1017 }
1018
1019 pub fn get_hook_kind_for_id(
1022 &self,
1023 identifier_id: IdentifierId,
1024 ) -> Result<Option<&HookKind>, CompilerDiagnostic> {
1025 let ty = &self.types[self.identifiers[identifier_id.0 as usize].type_.0 as usize];
1026 self.get_hook_kind_for_type(ty)
1027 }
1028}
1029
1030impl Default for Environment {
1031 fn default() -> Self {
1032 Self::new()
1033 }
1034}
1035
1036pub fn is_hook_name(name: &str) -> bool {
1039 if name.len() < 4 {
1040 return false;
1041 }
1042 if !name.starts_with("use") {
1043 return false;
1044 }
1045 let fourth_char = name.as_bytes()[3];
1046 fourth_char.is_ascii_uppercase() || fourth_char.is_ascii_digit()
1047}
1048
1049pub fn is_react_like_name(name: &str) -> bool {
1052 if name.is_empty() {
1053 return false;
1054 }
1055 let first_char = name.as_bytes()[0];
1056 if first_char.is_ascii_uppercase() {
1057 return true;
1058 }
1059 is_hook_name(name)
1060}
1061
1062#[cfg(test)]
1063mod tests {
1064 use super::*;
1065
1066 #[test]
1067 fn test_is_hook_name() {
1068 assert!(is_hook_name("useState"));
1069 assert!(is_hook_name("useEffect"));
1070 assert!(is_hook_name("useMyHook"));
1071 assert!(is_hook_name("use3rdParty"));
1072 assert!(!is_hook_name("use"));
1073 assert!(!is_hook_name("used"));
1074 assert!(!is_hook_name("useless"));
1075 assert!(!is_hook_name("User"));
1076 assert!(!is_hook_name("foo"));
1077 }
1078
1079 #[test]
1080 fn test_environment_has_globals() {
1081 let env = Environment::new();
1082 assert!(env.globals().contains_key("useState"));
1083 assert!(env.globals().contains_key("useEffect"));
1084 assert!(env.globals().contains_key("useRef"));
1085 assert!(env.globals().contains_key("Math"));
1086 assert!(env.globals().contains_key("console"));
1087 assert!(env.globals().contains_key("Array"));
1088 assert!(env.globals().contains_key("Object"));
1089 }
1090
1091 #[test]
1092 fn test_get_property_type_array() {
1093 let mut env = Environment::new();
1094 let array_type = Type::Object {
1095 shape_id: Some("BuiltInArray".to_string()),
1096 };
1097 let map_type = env.get_property_type(&array_type, "map").unwrap();
1098 assert!(map_type.is_some());
1099 let push_type = env.get_property_type(&array_type, "push").unwrap();
1100 assert!(push_type.is_some());
1101 let nonexistent = env
1102 .get_property_type(&array_type, "nonExistentMethod")
1103 .unwrap();
1104 assert!(nonexistent.is_none());
1105 }
1106
1107 #[test]
1108 fn test_get_function_signature() {
1109 let env = Environment::new();
1110 let use_state_type = env.globals().get("useState").unwrap();
1111 let sig = env.get_function_signature(use_state_type).unwrap();
1112 assert!(sig.is_some());
1113 let sig = sig.unwrap();
1114 assert!(sig.hook_kind.is_some());
1115 assert_eq!(sig.hook_kind.as_ref().unwrap(), &HookKind::UseState);
1116 }
1117
1118 #[test]
1119 fn test_get_global_declaration() {
1120 let mut env = Environment::new();
1121 let binding = NonLocalBinding::Global {
1123 name: "Math".to_string(),
1124 };
1125 let result = env.get_global_declaration(&binding, None).unwrap();
1126 assert!(result.is_some());
1127
1128 let binding = NonLocalBinding::ImportSpecifier {
1130 name: "useState".to_string(),
1131 module: "react".to_string(),
1132 imported: "useState".to_string(),
1133 };
1134 let result = env.get_global_declaration(&binding, None).unwrap();
1135 assert!(result.is_some());
1136
1137 let binding = NonLocalBinding::Global {
1139 name: "unknownThing".to_string(),
1140 };
1141 let result = env.get_global_declaration(&binding, None).unwrap();
1142 assert!(result.is_none());
1143
1144 let binding = NonLocalBinding::Global {
1146 name: "useCustom".to_string(),
1147 };
1148 let result = env.get_global_declaration(&binding, None).unwrap();
1149 assert!(result.is_some());
1150 }
1151}