1use std::collections::HashMap;
9
10use react_compiler_ast::expressions::*;
11use react_compiler_ast::jsx::JSXIdentifier;
12use react_compiler_ast::jsx::JSXOpeningElement;
13use react_compiler_ast::scope::ScopeId;
14use react_compiler_ast::scope::ScopeInfo;
15use react_compiler_ast::statements::FunctionDeclaration;
16use react_compiler_ast::visitor::AstWalker;
17use react_compiler_ast::visitor::Visitor;
18use react_compiler_hir::SourceLocation;
19
20use crate::FunctionNode;
21
22pub struct IdentifierLocEntry {
24 pub start: u32,
28 pub loc: SourceLocation,
29 pub is_jsx: bool,
30 pub opening_element_loc: Option<SourceLocation>,
33 pub is_declaration_name: bool,
38 pub in_type_annotation: bool,
44}
45
46pub type IdentifierLocIndex = HashMap<u32, IdentifierLocEntry>;
49
50struct IdentifierLocVisitor {
51 index: IdentifierLocIndex,
52 current_opening_element_loc: Option<SourceLocation>,
54}
55
56fn convert_loc(loc: &react_compiler_ast::common::SourceLocation) -> SourceLocation {
57 SourceLocation {
58 start: react_compiler_hir::Position {
59 line: loc.start.line,
60 column: loc.start.column,
61 index: loc.start.index,
62 },
63 end: react_compiler_hir::Position {
64 line: loc.end.line,
65 column: loc.end.column,
66 index: loc.end.index,
67 },
68 }
69}
70
71impl IdentifierLocVisitor {
72 fn insert_identifier(&mut self, node: &Identifier, is_declaration_name: bool) {
73 if let (Some(nid), Some(start), Some(loc)) =
74 (node.base.node_id, node.base.start, &node.base.loc)
75 {
76 self.index.insert(
77 nid,
78 IdentifierLocEntry {
79 start,
80 loc: convert_loc(loc),
81 is_jsx: false,
82 opening_element_loc: None,
83 is_declaration_name,
84 in_type_annotation: false,
85 },
86 );
87 }
88 }
89
90 fn walk_json_for_identifiers(&mut self, value: &serde_json::Value, in_annotation: bool) {
99 match value {
100 serde_json::Value::Object(obj) => {
101 let node_in_annotation = in_annotation
102 || matches!(
103 obj.get("type").and_then(|t| t.as_str()),
104 Some(
105 "TypeAnnotation"
106 | "TSTypeAnnotation"
107 | "TypeAlias"
108 | "TSTypeAliasDeclaration"
109 )
110 );
111 if let Some(serde_json::Value::String(ty)) = obj.get("type") {
112 if ty == "Identifier" || ty == "JSXIdentifier" {
113 if let (Some(nid), Some(start)) = (
114 obj.get("_nodeId").and_then(|s| s.as_u64()),
115 obj.get("start").and_then(|s| s.as_u64()),
116 ) {
117 if let Some(loc) = Self::extract_loc_from_json(obj) {
118 let is_jsx = ty == "JSXIdentifier";
119 self.index.entry(nid as u32).or_insert(IdentifierLocEntry {
120 start: start as u32,
121 loc,
122 is_jsx,
123 opening_element_loc: None,
124 is_declaration_name: false,
125 in_type_annotation: node_in_annotation,
126 });
127 }
128 }
129 }
130 }
131 for (_, v) in obj {
132 self.walk_json_for_identifiers(v, node_in_annotation);
133 }
134 }
135 serde_json::Value::Array(arr) => {
136 for v in arr {
137 self.walk_json_for_identifiers(v, in_annotation);
138 }
139 }
140 _ => {}
141 }
142 }
143
144 fn extract_loc_from_json(
145 obj: &serde_json::Map<String, serde_json::Value>,
146 ) -> Option<SourceLocation> {
147 let loc = obj.get("loc")?.as_object()?;
148 let start = loc.get("start")?.as_object()?;
149 let end = loc.get("end")?.as_object()?;
150 Some(SourceLocation {
151 start: react_compiler_hir::Position {
152 line: start.get("line")?.as_u64()? as u32,
153 column: start.get("column")?.as_u64()? as u32,
154 index: start
155 .get("index")
156 .and_then(|i| i.as_u64())
157 .map(|i| i as u32),
158 },
159 end: react_compiler_hir::Position {
160 line: end.get("line")?.as_u64()? as u32,
161 column: end.get("column")?.as_u64()? as u32,
162 index: end.get("index").and_then(|i| i.as_u64()).map(|i| i as u32),
163 },
164 })
165 }
166}
167
168impl<'ast> Visitor<'ast> for IdentifierLocVisitor {
169 fn enter_identifier(&mut self, node: &'ast Identifier, _scope_stack: &[ScopeId]) {
170 self.insert_identifier(node, false);
171 }
172
173 fn enter_jsx_identifier(&mut self, node: &'ast JSXIdentifier, _scope_stack: &[ScopeId]) {
174 if let (Some(nid), Some(start), Some(loc)) =
175 (node.base.node_id, node.base.start, &node.base.loc)
176 {
177 self.index.insert(
178 nid,
179 IdentifierLocEntry {
180 start,
181 loc: convert_loc(loc),
182 is_jsx: true,
183 opening_element_loc: self.current_opening_element_loc.clone(),
184 is_declaration_name: false,
185 in_type_annotation: false,
186 },
187 );
188 }
189 }
190
191 fn enter_jsx_opening_element(
192 &mut self,
193 node: &'ast JSXOpeningElement,
194 _scope_stack: &[ScopeId],
195 ) {
196 self.current_opening_element_loc = node.base.loc.as_ref().map(|loc| convert_loc(loc));
197 }
198
199 fn leave_jsx_opening_element(
200 &mut self,
201 _node: &'ast JSXOpeningElement,
202 _scope_stack: &[ScopeId],
203 ) {
204 self.current_opening_element_loc = None;
205 }
206
207 fn enter_function_declaration(
211 &mut self,
212 node: &'ast FunctionDeclaration,
213 _scope_stack: &[ScopeId],
214 ) {
215 if let Some(id) = &node.id {
216 self.insert_identifier(id, true);
217 }
218 }
219
220 fn enter_function_expression(
221 &mut self,
222 node: &'ast FunctionExpression,
223 _scope_stack: &[ScopeId],
224 ) {
225 if let Some(id) = &node.id {
226 self.insert_identifier(id, true);
227 }
228 }
229
230 fn enter_class_declaration(
231 &mut self,
232 node: &'ast react_compiler_ast::statements::ClassDeclaration,
233 _scope_stack: &[ScopeId],
234 ) {
235 if let Some(id) = &node.id {
236 self.insert_identifier(id, true);
237 }
238 for member in &node.body.body {
242 self.walk_json_for_identifiers(member, false);
243 }
244 }
245
246 fn enter_class_expression(
247 &mut self,
248 node: &'ast react_compiler_ast::expressions::ClassExpression,
249 _scope_stack: &[ScopeId],
250 ) {
251 if let Some(id) = &node.id {
252 self.insert_identifier(id, true);
253 }
254 for member in &node.body.body {
256 self.walk_json_for_identifiers(member, false);
257 }
258 }
259}
260
261pub fn build_identifier_loc_index(
263 func: &FunctionNode<'_>,
264 scope_info: &ScopeInfo,
265) -> IdentifierLocIndex {
266 let func_scope = scope_info
267 .resolve_scope_for_node(func.node_id())
268 .unwrap_or(scope_info.program_scope);
269
270 let mut visitor = IdentifierLocVisitor {
271 index: HashMap::new(),
272 current_opening_element_loc: None,
273 };
274 let mut walker = AstWalker::with_initial_scope(scope_info, func_scope);
275
276 match func {
279 FunctionNode::FunctionDeclaration(d) => {
280 if let Some(id) = &d.id {
281 visitor.enter_identifier(id, &[]);
282 }
283 for param in &d.params {
284 walker.walk_pattern(&mut visitor, param);
285 }
286 walker.walk_block_statement(&mut visitor, &d.body);
287 }
288 FunctionNode::FunctionExpression(e) => {
289 if let Some(id) = &e.id {
290 visitor.enter_identifier(id, &[]);
291 }
292 for param in &e.params {
293 walker.walk_pattern(&mut visitor, param);
294 }
295 walker.walk_block_statement(&mut visitor, &e.body);
296 }
297 FunctionNode::ArrowFunctionExpression(a) => {
298 for param in &a.params {
299 walker.walk_pattern(&mut visitor, param);
300 }
301 match a.body.as_ref() {
302 ArrowFunctionBody::BlockStatement(block) => {
303 walker.walk_block_statement(&mut visitor, block);
304 }
305 ArrowFunctionBody::Expression(expr) => {
306 walker.walk_expression(&mut visitor, expr);
307 }
308 }
309 }
310 }
311
312 let body_json: Option<serde_json::Value> = match func {
321 FunctionNode::FunctionDeclaration(d) => serde_json::to_value(&d.body).ok(),
322 FunctionNode::FunctionExpression(e) => serde_json::to_value(&e.body).ok(),
323 FunctionNode::ArrowFunctionExpression(a) => serde_json::to_value(&a.body).ok(),
324 };
325 if let Some(json) = body_json {
326 visitor.walk_json_for_identifiers(&json, false);
327 }
328
329 visitor.index
330}