1use rustc_hash::FxHashMap;
2use rustc_hash::FxHashSet;
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: FxHashSet<u32>,
84
85 hoisted_identifiers: FxHashSet<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: FxHashMap<String, Option<Global>>,
99 module_type_errors: FxHashMap<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<FxHashSet<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: FxHashMap<String, Option<Global>> = FxHashMap::default();
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: FxHashSet::default(),
194 hoisted_identifiers: FxHashSet::default(),
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: FxHashMap::default(),
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: FxHashSet::default(),
241 hoisted_identifiers: FxHashSet::default(),
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(
269 &mut self,
270 start: EvaluationOrder,
271 end: EvaluationOrder,
272 ) -> MutableRange {
273 let id = MutableRangeId(self.next_mutable_range_id_counter);
274 self.next_mutable_range_id_counter += 1;
275 MutableRange { id, start, end }
276 }
277
278 pub fn next_identifier_id(&mut self) -> IdentifierId {
281 let id = IdentifierId(self.identifiers.len() as u32);
282 let type_id = self.make_type();
283 let mutable_range = self.new_mutable_range(EvaluationOrder(0), EvaluationOrder(0));
284 self.identifiers.push(Identifier {
285 id,
286 declaration_id: DeclarationId(id.0),
287 name: None,
288 mutable_range,
289 scope: None,
290 type_: type_id,
291 loc: None,
292 });
293 id
294 }
295
296 pub fn next_scope_id(&mut self) -> ScopeId {
298 let id = ScopeId(self.next_scope_id_counter);
299 self.next_scope_id_counter += 1;
300 let range = self.new_mutable_range(EvaluationOrder(0), EvaluationOrder(0));
301 self.scopes.push(ReactiveScope {
302 id,
303 range,
304 dependencies: Vec::new(),
305 declarations: Vec::new(),
306 reassignments: Vec::new(),
307 early_return_value: None,
308 merged: Vec::new(),
309 loc: None,
310 });
311 id
312 }
313
314 pub fn next_type_id(&mut self) -> TypeId {
316 let id = TypeId(self.types.len() as u32);
317 self.types.push(Type::TypeVar { id });
318 id
319 }
320
321 pub fn make_type(&mut self) -> TypeId {
323 self.next_type_id()
324 }
325
326 pub fn add_function(&mut self, func: HirFunction) -> FunctionId {
327 let id = FunctionId(self.functions.len() as u32);
328 self.functions.push(func);
329 id
330 }
331
332 pub fn record_error(&mut self, detail: CompilerErrorDetail) -> Result<(), CompilerError> {
333 if detail.category == ErrorCategory::Invariant {
334 let detail_clone = detail.clone();
335 self.errors.push_error_detail(detail);
336 let mut err = CompilerError::new();
337 err.push_error_detail(detail_clone);
338 return Err(err);
339 }
340 self.errors.push_error_detail(detail);
341 Ok(())
342 }
343
344 pub fn record_diagnostic(&mut self, diagnostic: CompilerDiagnostic) {
345 self.errors.push_diagnostic(diagnostic);
346 }
347
348 pub fn has_errors(&self) -> bool {
349 self.errors.has_any_errors()
350 }
351
352 pub fn error_count(&self) -> usize {
353 self.errors.details.len()
354 }
355
356 pub fn has_invariant_errors(&self) -> bool {
360 self.errors.has_invariant_errors()
361 }
362
363 pub fn errors(&self) -> &CompilerError {
364 &self.errors
365 }
366
367 pub fn take_errors(&mut self) -> CompilerError {
368 let mut errors = std::mem::take(&mut self.errors);
369 errors.is_thrown = false;
372 errors
373 }
374
375 pub fn take_errors_since(&mut self, since_count: usize) -> CompilerError {
378 let mut taken = CompilerError::new();
379 if self.errors.details.len() > since_count {
380 taken.details = self.errors.details.split_off(since_count);
381 }
382 taken
383 }
384
385 pub fn take_invariant_errors(&mut self) -> CompilerError {
389 let mut invariant = CompilerError::new();
390 let mut remaining = CompilerError::new();
391 let old = std::mem::take(&mut self.errors);
392 for detail in old.details {
393 let is_invariant = match &detail {
394 react_compiler_diagnostics::CompilerErrorOrDiagnostic::Diagnostic(d) => {
395 d.category == react_compiler_diagnostics::ErrorCategory::Invariant
396 }
397 react_compiler_diagnostics::CompilerErrorOrDiagnostic::ErrorDetail(d) => {
398 d.category == react_compiler_diagnostics::ErrorCategory::Invariant
399 }
400 };
401 if is_invariant {
402 invariant.details.push(detail);
403 } else {
404 remaining.details.push(detail);
405 }
406 }
407 self.errors = remaining;
408 invariant
409 }
410
411 pub fn has_todo_errors(&self) -> bool {
414 self.errors.details.iter().any(|d| match d {
415 react_compiler_diagnostics::CompilerErrorOrDiagnostic::Diagnostic(d) => {
416 d.category == react_compiler_diagnostics::ErrorCategory::Todo
417 }
418 react_compiler_diagnostics::CompilerErrorOrDiagnostic::ErrorDetail(d) => {
419 d.category == react_compiler_diagnostics::ErrorCategory::Todo
420 }
421 })
422 }
423
424 pub fn take_thrown_errors(&mut self) -> CompilerError {
427 let mut thrown = CompilerError::new();
428 let mut remaining = CompilerError::new();
429 let old = std::mem::take(&mut self.errors);
430 for detail in old.details {
431 let is_thrown = match &detail {
432 react_compiler_diagnostics::CompilerErrorOrDiagnostic::Diagnostic(d) => {
433 d.category == react_compiler_diagnostics::ErrorCategory::Invariant
434 || d.category == react_compiler_diagnostics::ErrorCategory::Todo
435 }
436 react_compiler_diagnostics::CompilerErrorOrDiagnostic::ErrorDetail(d) => {
437 d.category == react_compiler_diagnostics::ErrorCategory::Invariant
438 || d.category == react_compiler_diagnostics::ErrorCategory::Todo
439 }
440 };
441 if is_thrown {
442 thrown.details.push(detail);
443 } else {
444 remaining.details.push(detail);
445 }
446 }
447 self.errors = remaining;
448 thrown
449 }
450
451 pub fn is_hoisted_identifier(&self, binding_id: u32) -> bool {
453 self.hoisted_identifiers.contains(&binding_id)
454 }
455
456 pub fn add_hoisted_identifier(&mut self, binding_id: u32) {
458 self.hoisted_identifiers.insert(binding_id);
459 }
460
461 pub fn get_global_declaration(
470 &mut self,
471 binding: &NonLocalBinding,
472 loc: Option<SourceLocation>,
473 ) -> Result<Option<Global>, CompilerError> {
474 match binding {
475 NonLocalBinding::ModuleLocal { name, .. } => {
476 if is_hook_name(name) {
477 Ok(Some(self.get_custom_hook_type()))
478 } else {
479 Ok(None)
480 }
481 }
482 NonLocalBinding::Global { name, .. } => {
483 if let Some(ty) = self.globals.get(name) {
484 return Ok(Some(ty.clone()));
485 }
486 if is_hook_name(name) {
487 Ok(Some(self.get_custom_hook_type()))
488 } else {
489 Ok(None)
490 }
491 }
492 NonLocalBinding::ImportSpecifier {
493 name,
494 module,
495 imported,
496 } => {
497 if self.is_known_react_module(module) {
498 if let Some(ty) = self.globals.get(imported) {
499 return Ok(Some(ty.clone()));
500 }
501 if is_hook_name(imported) || is_hook_name(name) {
502 return Ok(Some(self.get_custom_hook_type()));
503 }
504 return Ok(None);
505 }
506
507 let module_type = self.resolve_module_type(module);
510
511 if let Some(errors) = self.module_type_errors.remove(module.as_str()) {
513 if let Some(first_error) = errors.into_iter().next() {
514 self.record_error(
515 CompilerErrorDetail::new(
516 ErrorCategory::Config,
517 "Invalid type configuration for module",
518 )
519 .with_description(format!("{}", first_error))
520 .with_loc(loc),
521 )?;
522 }
523 }
524
525 if let Some(module_type) = module_type {
526 if let Some(imported_type) =
527 Self::get_property_type_from_shapes(&self.shapes, &module_type, imported)
528 {
529 return Ok(Some(imported_type));
530 }
531 }
532
533 if is_hook_name(imported) || is_hook_name(name) {
534 Ok(Some(self.get_custom_hook_type()))
535 } else {
536 Ok(None)
537 }
538 }
539 NonLocalBinding::ImportDefault { name, module }
540 | NonLocalBinding::ImportNamespace { name, module } => {
541 let is_default = matches!(binding, NonLocalBinding::ImportDefault { .. });
542
543 if self.is_known_react_module(module) {
544 if let Some(ty) = self.globals.get(name) {
545 return Ok(Some(ty.clone()));
546 }
547 if is_hook_name(name) {
548 return Ok(Some(self.get_custom_hook_type()));
549 }
550 return Ok(None);
551 }
552
553 let module_type = self.resolve_module_type(module);
554
555 if let Some(errors) = self.module_type_errors.remove(module.as_str()) {
557 if let Some(first_error) = errors.into_iter().next() {
558 self.record_error(
559 CompilerErrorDetail::new(
560 ErrorCategory::Config,
561 "Invalid type configuration for module",
562 )
563 .with_description(format!("{}", first_error))
564 .with_loc(loc),
565 )?;
566 }
567 }
568
569 if let Some(module_type) = module_type {
570 let imported_type = if is_default {
571 Self::get_property_type_from_shapes(&self.shapes, &module_type, "default")
572 } else {
573 Some(module_type)
574 };
575 if let Some(imported_type) = imported_type {
576 let expect_hook = is_hook_name(module);
578 let is_hook = self
579 .get_hook_kind_for_type(&imported_type)
580 .ok()
581 .flatten()
582 .is_some();
583 if expect_hook != is_hook {
584 self.record_error(
585 CompilerErrorDetail::new(
586 ErrorCategory::Config,
587 "Invalid type configuration for module",
588 )
589 .with_description(format!(
590 "Expected type for `import ... from '{}'` {} based on the module name",
591 module,
592 if expect_hook { "to be a hook" } else { "not to be a hook" }
593 ))
594 .with_loc(loc),
595 )?;
596 }
597 return Ok(Some(imported_type));
598 }
599 }
600
601 if is_hook_name(name) {
602 Ok(Some(self.get_custom_hook_type()))
603 } else {
604 Ok(None)
605 }
606 }
607 }
608 }
609
610 fn get_property_type_from_shapes(
614 shapes: &ShapeRegistry,
615 receiver: &Type,
616 property: &str,
617 ) -> Option<Type> {
618 let shape_id = match receiver {
619 Type::Object { shape_id } | Type::Function { shape_id, .. } => shape_id.as_deref(),
620 _ => None,
621 };
622 if let Some(shape_id) = shape_id {
623 let shape = shapes.get(shape_id)?;
624 if let Some(ty) = shape.properties.get(property) {
625 return Some(ty.clone());
626 }
627 if let Some(ty) = shape.properties.get("*") {
628 return Some(ty.clone());
629 }
630 }
634 None
635 }
636
637 pub fn get_property_type(
640 &mut self,
641 receiver: &Type,
642 property: &str,
643 ) -> Result<Option<Type>, CompilerDiagnostic> {
644 let shape_id = match receiver {
645 Type::Object { shape_id } | Type::Function { shape_id, .. } => shape_id.as_deref(),
646 _ => None,
647 };
648 if let Some(shape_id) = shape_id {
649 let shape = self.shapes.get(shape_id).ok_or_else(|| {
650 CompilerDiagnostic::new(
651 ErrorCategory::Invariant,
652 format!(
653 "[HIR] Forget internal error: cannot resolve shape {}",
654 shape_id
655 ),
656 None,
657 )
658 })?;
659 if let Some(ty) = shape.properties.get(property) {
660 return Ok(Some(ty.clone()));
661 }
662 if let Some(ty) = shape.properties.get("*") {
664 return Ok(Some(ty.clone()));
665 }
666 if is_hook_name(property) {
668 return Ok(Some(self.get_custom_hook_type()));
669 }
670 return Ok(None);
671 }
672 if is_hook_name(property) {
674 return Ok(Some(self.get_custom_hook_type()));
675 }
676 Ok(None)
677 }
678
679 pub fn get_property_type_numeric(
682 &self,
683 receiver: &Type,
684 ) -> Result<Option<Type>, CompilerDiagnostic> {
685 let shape_id = match receiver {
686 Type::Object { shape_id } | Type::Function { shape_id, .. } => shape_id.as_deref(),
687 _ => None,
688 };
689 if let Some(shape_id) = shape_id {
690 let shape = self.shapes.get(shape_id).ok_or_else(|| {
691 CompilerDiagnostic::new(
692 ErrorCategory::Invariant,
693 format!(
694 "[HIR] Forget internal error: cannot resolve shape {}",
695 shape_id
696 ),
697 None,
698 )
699 })?;
700 return Ok(shape.properties.get("*").cloned());
701 }
702 Ok(None)
703 }
704
705 pub fn get_fallthrough_property_type(
708 &self,
709 receiver: &Type,
710 ) -> Result<Option<Type>, CompilerDiagnostic> {
711 let shape_id = match receiver {
712 Type::Object { shape_id } | Type::Function { shape_id, .. } => shape_id.as_deref(),
713 _ => None,
714 };
715 if let Some(shape_id) = shape_id {
716 let shape = self.shapes.get(shape_id).ok_or_else(|| {
717 CompilerDiagnostic::new(
718 ErrorCategory::Invariant,
719 format!(
720 "[HIR] Forget internal error: cannot resolve shape {}",
721 shape_id
722 ),
723 None,
724 )
725 })?;
726 return Ok(shape.properties.get("*").cloned());
727 }
728 Ok(None)
729 }
730
731 pub fn get_function_signature(
734 &self,
735 ty: &Type,
736 ) -> Result<Option<&FunctionSignature>, CompilerDiagnostic> {
737 let shape_id = match ty {
738 Type::Function { shape_id, .. } => shape_id.as_deref(),
739 _ => return Ok(None),
740 };
741 if let Some(shape_id) = shape_id {
742 let shape = self.shapes.get(shape_id).ok_or_else(|| {
743 CompilerDiagnostic::new(
744 ErrorCategory::Invariant,
745 format!(
746 "[HIR] Forget internal error: cannot resolve shape {}",
747 shape_id
748 ),
749 None,
750 )
751 })?;
752 return Ok(shape.function_type.as_ref());
753 }
754 Ok(None)
755 }
756
757 pub fn get_hook_kind_for_type(
760 &self,
761 ty: &Type,
762 ) -> Result<Option<&HookKind>, CompilerDiagnostic> {
763 Ok(self
764 .get_function_signature(ty)?
765 .and_then(|sig| sig.hook_kind.as_ref()))
766 }
767
768 fn resolve_module_type(&mut self, module_name: &str) -> Option<Global> {
772 if let Some(cached) = self.module_types.get(module_name) {
773 return cached.clone();
774 }
775
776 let module_config = self
778 .config
779 .module_type_provider
780 .as_ref()
781 .and_then(|map| map.get(module_name).cloned())
782 .or_else(|| default_module_type_provider(module_name));
783
784 let module_type = module_config.map(|config| {
785 let mut type_errors: Vec<String> = Vec::new();
786 let ty = globals::install_type_config_with_errors(
787 &mut self.globals,
788 &mut self.shapes,
789 &config,
790 module_name,
791 (),
792 &mut type_errors,
793 );
794 for err in type_errors {
796 self.module_type_errors
797 .entry(module_name.to_string())
798 .or_default()
799 .push(err);
800 }
801 ty
802 });
803 self.module_types
804 .insert(module_name.to_string(), module_type.clone());
805 module_type
806 }
807
808 fn is_known_react_module(&self, module_name: &str) -> bool {
809 let lower = module_name.to_lowercase();
810 lower == "react" || lower == "react-dom"
811 }
812
813 fn get_custom_hook_type(&mut self) -> Global {
814 if self.config.enable_assume_hooks_follow_rules_of_react {
815 if self.default_nonmutating_hook.is_none() {
816 self.default_nonmutating_hook = Some(default_nonmutating_hook(&mut self.shapes));
817 }
818 self.default_nonmutating_hook.clone().unwrap()
819 } else {
820 if self.default_mutating_hook.is_none() {
821 self.default_mutating_hook = Some(default_mutating_hook(&mut self.shapes));
822 }
823 self.default_mutating_hook.clone().unwrap()
824 }
825 }
826
827 pub fn get_custom_hook_type_opt(&mut self) -> Option<Global> {
830 Some(self.get_custom_hook_type())
831 }
832
833 pub fn shapes(&self) -> &ShapeRegistry {
835 &self.shapes
836 }
837
838 pub fn globals(&self) -> &GlobalRegistry {
840 &self.globals
841 }
842
843 pub fn generate_globally_unique_identifier_name(&mut self, name: Option<&str>) -> String {
853 let base = name.unwrap_or("temp");
854 let mut dashed = String::new();
859 for c in base.chars() {
860 if c.is_ascii_alphanumeric() || c == '_' || c == '$' {
861 dashed.push(c);
862 } else {
863 dashed.push('-');
864 }
865 }
866 let trimmed = dashed.trim_start_matches(|c: char| c == '-' || c.is_ascii_digit());
868 let mut camel = String::new();
870 let mut chars = trimmed.chars().peekable();
871 while let Some(c) = chars.next() {
872 if c == '-' {
873 while chars.peek() == Some(&'-') {
874 chars.next();
875 }
876 if let Some(next) = chars.next() {
877 for uc in next.to_uppercase() {
878 camel.push(uc);
879 }
880 }
881 } else {
882 camel.push(c);
883 }
884 }
885 if camel.is_empty() {
886 camel = "temp".to_string();
887 }
888 let stripped = camel.trim_start_matches('_');
890 let stripped = stripped.trim_end_matches(|c: char| c.is_ascii_digit());
891 let uid_base = if stripped.is_empty() {
892 "temp"
893 } else {
894 stripped
895 };
896
897 if self.uid_known_names.is_none() {
900 let mut known = FxHashSet::default();
901 for id in &self.identifiers {
902 if let Some(name) = &id.name {
903 known.insert(name.value().to_string());
904 }
905 }
906 self.uid_known_names = Some(known);
907 }
908
909 let mut i = 1u32;
911 let uid = loop {
912 let candidate = if i == 1 {
913 format!("_{}", uid_base)
914 } else {
915 format!("_{}{}", uid_base, i)
916 };
917 i += 1;
918 if !self.uid_known_names.as_ref().unwrap().contains(&candidate) {
919 break candidate;
920 }
921 };
922
923 self.uid_known_names.as_mut().unwrap().insert(uid.clone());
925
926 uid
927 }
928
929 pub fn seed_uid_known_names(&mut self, names: &FxHashSet<String>) {
933 match &mut self.uid_known_names {
934 Some(existing) => existing.extend(names.iter().cloned()),
935 None => self.uid_known_names = Some(names.clone()),
936 }
937 }
938
939 pub fn take_uid_known_names(&mut self) -> Option<FxHashSet<String>> {
941 self.uid_known_names.take()
942 }
943
944 pub fn outline_function(&mut self, func: HirFunction, fn_type: Option<ReactFunctionType>) {
947 self.outlined_functions
948 .push(OutlinedFunctionEntry { func, fn_type });
949 }
950
951 pub fn get_outlined_functions(&self) -> &[OutlinedFunctionEntry] {
953 &self.outlined_functions
954 }
955
956 pub fn take_outlined_functions(&mut self) -> Vec<OutlinedFunctionEntry> {
958 std::mem::take(&mut self.outlined_functions)
959 }
960
961 pub fn enable_memoization(&self) -> bool {
965 match self.output_mode {
966 OutputMode::Client | OutputMode::Lint => true,
967 OutputMode::Ssr => false,
968 }
969 }
970
971 pub fn enable_validations(&self) -> bool {
974 match self.output_mode {
975 OutputMode::Client | OutputMode::Lint | OutputMode::Ssr => true,
976 }
977 }
978
979 pub fn identifier_name_for_id(&self, id: IdentifierId) -> Option<String> {
993 let ident = &self.identifiers[id.0 as usize];
994 if let Some(name) = &ident.name {
995 return Some(name.value().to_string());
996 }
997 let decl_id = ident.declaration_id;
999 for other in &self.identifiers {
1000 if other.declaration_id == decl_id {
1001 if let Some(IdentifierName::Named(name)) = &other.name {
1002 return Some(name.clone());
1003 }
1004 }
1005 }
1006 None
1007 }
1008
1009 pub fn has_no_alias_signature(&self, identifier_id: IdentifierId) -> bool {
1016 let ty = &self.types[self.identifiers[identifier_id.0 as usize].type_.0 as usize];
1017 self.get_function_signature(ty)
1018 .ok()
1019 .flatten()
1020 .map_or(false, |sig| sig.no_alias)
1021 }
1022
1023 pub fn get_hook_kind_for_id(
1026 &self,
1027 identifier_id: IdentifierId,
1028 ) -> Result<Option<&HookKind>, CompilerDiagnostic> {
1029 let ty = &self.types[self.identifiers[identifier_id.0 as usize].type_.0 as usize];
1030 self.get_hook_kind_for_type(ty)
1031 }
1032}
1033
1034impl Default for Environment {
1035 fn default() -> Self {
1036 Self::new()
1037 }
1038}
1039
1040pub fn is_hook_name(name: &str) -> bool {
1043 if name.len() < 4 {
1044 return false;
1045 }
1046 if !name.starts_with("use") {
1047 return false;
1048 }
1049 let fourth_char = name.as_bytes()[3];
1050 fourth_char.is_ascii_uppercase() || fourth_char.is_ascii_digit()
1051}
1052
1053pub fn is_react_like_name(name: &str) -> bool {
1056 if name.is_empty() {
1057 return false;
1058 }
1059 let first_char = name.as_bytes()[0];
1060 if first_char.is_ascii_uppercase() {
1061 return true;
1062 }
1063 is_hook_name(name)
1064}
1065
1066#[cfg(test)]
1067mod tests {
1068 use super::*;
1069
1070 #[test]
1071 fn test_is_hook_name() {
1072 assert!(is_hook_name("useState"));
1073 assert!(is_hook_name("useEffect"));
1074 assert!(is_hook_name("useMyHook"));
1075 assert!(is_hook_name("use3rdParty"));
1076 assert!(!is_hook_name("use"));
1077 assert!(!is_hook_name("used"));
1078 assert!(!is_hook_name("useless"));
1079 assert!(!is_hook_name("User"));
1080 assert!(!is_hook_name("foo"));
1081 }
1082
1083 #[test]
1084 fn test_environment_has_globals() {
1085 let env = Environment::new();
1086 assert!(env.globals().contains_key("useState"));
1087 assert!(env.globals().contains_key("useEffect"));
1088 assert!(env.globals().contains_key("useRef"));
1089 assert!(env.globals().contains_key("Math"));
1090 assert!(env.globals().contains_key("console"));
1091 assert!(env.globals().contains_key("Array"));
1092 assert!(env.globals().contains_key("Object"));
1093 }
1094
1095 #[test]
1096 fn test_get_property_type_array() {
1097 let mut env = Environment::new();
1098 let array_type = Type::Object {
1099 shape_id: Some("BuiltInArray".to_string()),
1100 };
1101 let map_type = env.get_property_type(&array_type, "map").unwrap();
1102 assert!(map_type.is_some());
1103 let push_type = env.get_property_type(&array_type, "push").unwrap();
1104 assert!(push_type.is_some());
1105 let nonexistent = env
1106 .get_property_type(&array_type, "nonExistentMethod")
1107 .unwrap();
1108 assert!(nonexistent.is_none());
1109 }
1110
1111 #[test]
1112 fn test_get_function_signature() {
1113 let env = Environment::new();
1114 let use_state_type = env.globals().get("useState").unwrap();
1115 let sig = env.get_function_signature(use_state_type).unwrap();
1116 assert!(sig.is_some());
1117 let sig = sig.unwrap();
1118 assert!(sig.hook_kind.is_some());
1119 assert_eq!(sig.hook_kind.as_ref().unwrap(), &HookKind::UseState);
1120 }
1121
1122 #[test]
1123 fn test_get_global_declaration() {
1124 let mut env = Environment::new();
1125 let binding = NonLocalBinding::Global {
1127 name: "Math".to_string(),
1128 };
1129 let result = env.get_global_declaration(&binding, None).unwrap();
1130 assert!(result.is_some());
1131
1132 let binding = NonLocalBinding::ImportSpecifier {
1134 name: "useState".to_string(),
1135 module: "react".to_string(),
1136 imported: "useState".to_string(),
1137 };
1138 let result = env.get_global_declaration(&binding, None).unwrap();
1139 assert!(result.is_some());
1140
1141 let binding = NonLocalBinding::Global {
1143 name: "unknownThing".to_string(),
1144 };
1145 let result = env.get_global_declaration(&binding, None).unwrap();
1146 assert!(result.is_none());
1147
1148 let binding = NonLocalBinding::Global {
1150 name: "useCustom".to_string(),
1151 };
1152 let result = env.get_global_declaration(&binding, None).unwrap();
1153 assert!(result.is_some());
1154 }
1155}