@@ -147,137 +147,162 @@ fn scan_statements(
147147 }
148148 }
149149}
150-
151150/// Check an expression tree for Node.js API patterns.
152151fn check_expression ( expr : & Expression < ' _ > , source : & str , detected : & mut Vec < DetectedApi > ) {
153152 match expr {
154- // require('fs'), require('child_process'), etc.
155153 Expression :: CallExpression ( call) => {
156- if let Expression :: Identifier ( ident) = & call. callee {
157- if ident. name == "require" {
158- if let Some ( Argument :: StringLiteral ( lit) ) = call. arguments . first ( ) {
159- if let Some ( api) = classify_import_specifier ( lit. value . as_str ( ) ) {
160- let line = line_number_at_offset ( source, call. span . start ) ;
161- detected. push ( api. with_line ( line) ) ;
162- }
163- }
164- }
165- }
154+ check_call_expression ( call, source, detected) ;
155+ }
156+ Expression :: StaticMemberExpression ( member) => {
157+ check_static_member_expression ( member, source, detected) ;
158+ }
159+ Expression :: Identifier ( ident) => {
160+ check_identifier ( ident, source, detected) ;
161+ }
162+ Expression :: AssignmentExpression ( assign) => {
163+ check_assignment_expression ( assign, source, detected) ;
164+ }
165+ _ => { }
166+ }
167+ }
166168
167- // Check for shimmable patterns: Buffer.from(), Buffer.alloc(), crypto.randomBytes()
168- if let Expression :: StaticMemberExpression ( member) = & call. callee {
169- let prop = member. property . name . as_str ( ) ;
170- if let Expression :: Identifier ( obj) = & member. object {
171- let obj_name = obj. name . as_str ( ) ;
172- if obj_name == "Buffer" && ( prop == "from" || prop == "alloc" ) {
173- let line = line_number_at_offset ( source, call. span . start ) ;
174- detected. push ( DetectedApi {
175- pattern : format ! ( "Buffer.{prop}()" ) ,
176- line,
177- compatibility : Compatibility :: Shimmable ,
178- } ) ;
179- }
180- if obj_name == "crypto" && prop == "randomBytes" {
181- let line = line_number_at_offset ( source, call. span . start ) ;
182- detected. push ( DetectedApi {
183- pattern : "crypto.randomBytes()" . into ( ) ,
184- line,
185- compatibility : Compatibility :: Shimmable ,
186- } ) ;
187- }
169+ fn check_call_expression (
170+ call : & oxc_ast:: ast:: CallExpression < ' _ > ,
171+ source : & str ,
172+ detected : & mut Vec < DetectedApi > ,
173+ ) {
174+ // require('fs'), require('child_process'), etc.
175+ if let Expression :: Identifier ( ident) = & call. callee {
176+ if ident. name == "require" {
177+ if let Some ( Argument :: StringLiteral ( lit) ) = call. arguments . first ( ) {
178+ if let Some ( api) = classify_import_specifier ( lit. value . as_str ( ) ) {
179+ let line = line_number_at_offset ( source, call. span . start ) ;
180+ detected. push ( api. with_line ( line) ) ;
188181 }
189182 }
183+ }
184+ }
190185
191- // process.exit()
192- if let Expression :: StaticMemberExpression ( member) = & call. callee {
193- if member. property . name == "exit" {
194- if let Expression :: Identifier ( obj) = & member. object {
195- if obj. name == "process" {
196- let line = line_number_at_offset ( source, call. span . start ) ;
197- detected. push ( DetectedApi {
198- pattern : "process.exit()" . into ( ) ,
199- line,
200- compatibility : Compatibility :: NeedsManualAttention ,
201- } ) ;
202- }
203- }
204- }
186+ // Check for shimmable patterns: Buffer.from(), Buffer.alloc(), crypto.randomBytes()
187+ if let Expression :: StaticMemberExpression ( member) = & call. callee {
188+ let prop = member. property . name . as_str ( ) ;
189+ if let Expression :: Identifier ( obj) = & member. object {
190+ let obj_name = obj. name . as_str ( ) ;
191+ if obj_name == "Buffer" && ( prop == "from" || prop == "alloc" ) {
192+ let line = line_number_at_offset ( source, call. span . start ) ;
193+ detected. push ( DetectedApi {
194+ pattern : format ! ( "Buffer.{prop}()" ) ,
195+ line,
196+ compatibility : Compatibility :: Shimmable ,
197+ } ) ;
205198 }
206-
207- // Recurse into call arguments
208- for arg in & call. arguments {
209- if let Argument :: FunctionExpression ( fn_expr) = arg {
210- if let Some ( body) = & fn_expr. body {
211- scan_statements ( & body. statements , source, detected) ;
212- }
213- }
214- if let Argument :: ArrowFunctionExpression ( arrow) = arg {
215- scan_statements ( & arrow. body . statements , source, detected) ;
216- }
199+ if obj_name == "crypto" && prop == "randomBytes" {
200+ let line = line_number_at_offset ( source, call. span . start ) ;
201+ detected. push ( DetectedApi {
202+ pattern : "crypto.randomBytes()" . into ( ) ,
203+ line,
204+ compatibility : Compatibility :: Shimmable ,
205+ } ) ;
217206 }
218207 }
208+ }
219209
220- // process.env access (shimmable → Forge.env())
221- Expression :: StaticMemberExpression ( member) if member. property . name == "env" => {
210+ // process.exit()
211+ if let Expression :: StaticMemberExpression ( member) = & call. callee {
212+ if member. property . name == "exit" {
222213 if let Expression :: Identifier ( obj) = & member. object {
223214 if obj. name == "process" {
224- let line = line_number_at_offset ( source, member . span . start ) ;
215+ let line = line_number_at_offset ( source, call . span . start ) ;
225216 detected. push ( DetectedApi {
226- pattern : "process.env " . into ( ) ,
217+ pattern : "process.exit() " . into ( ) ,
227218 line,
228- compatibility : Compatibility :: Shimmable ,
219+ compatibility : Compatibility :: NeedsManualAttention ,
229220 } ) ;
230221 }
231222 }
232223 }
233- Expression :: StaticMemberExpression ( _) => {
234- // __dirname, __filename as member access targets are covered below
235- }
236- Expression :: StaticMemberExpression ( _) => { }
224+ }
237225
238- // Standalone identifiers: __dirname, __filename, Buffer (as global)
239- Expression :: Identifier ( ident) => {
240- let name = ident. name . as_str ( ) ;
241- match name {
242- "__dirname" | "__filename" => {
243- let line = line_number_at_offset ( source, ident. span . start ) ;
244- detected. push ( DetectedApi {
245- pattern : name. to_string ( ) ,
246- line,
247- compatibility : Compatibility :: NeedsManualAttention ,
248- } ) ;
249- }
250- "Buffer" => {
251- let line = line_number_at_offset ( source, ident. span . start ) ;
252- detected. push ( DetectedApi {
253- pattern : "Buffer (global)" . into ( ) ,
254- line,
255- compatibility : Compatibility :: NeedsManualAttention ,
256- } ) ;
257- }
258- _ => { }
226+ // Recurse into call arguments
227+ for arg in & call. arguments {
228+ if let Argument :: FunctionExpression ( fn_expr) = arg {
229+ if let Some ( body) = & fn_expr. body {
230+ scan_statements ( & body. statements , source, detected) ;
259231 }
260232 }
233+ if let Argument :: ArrowFunctionExpression ( arrow) = arg {
234+ scan_statements ( & arrow. body . statements , source, detected) ;
235+ }
236+ }
237+ }
261238
262- // module.exports (CJS export pattern)
263- Expression :: AssignmentExpression ( assign) => {
264- if let Some ( member) = extract_member_pattern ( & assign. left ) {
265- if member == "module.exports" {
266- let line = line_number_at_offset ( source, assign. span . start ) ;
267- detected. push ( DetectedApi {
268- pattern : "module.exports" . into ( ) ,
269- line,
270- compatibility : Compatibility :: NeedsManualAttention ,
271- } ) ;
272- }
239+ fn check_static_member_expression (
240+ member : & oxc_ast:: ast:: StaticMemberExpression < ' _ > ,
241+ source : & str ,
242+ detected : & mut Vec < DetectedApi > ,
243+ ) {
244+ // process.env access (shimmable → Forge.env())
245+ if member. property . name == "env" {
246+ if let Expression :: Identifier ( obj) = & member. object {
247+ if obj. name == "process" {
248+ let line = line_number_at_offset ( source, member. span . start ) ;
249+ detected. push ( DetectedApi {
250+ pattern : "process.env" . into ( ) ,
251+ line,
252+ compatibility : Compatibility :: Shimmable ,
253+ } ) ;
273254 }
274- check_expression ( & assign. right , source, detected) ;
275255 }
256+ }
257+ // __dirname, __filename as member access targets are covered below
258+ }
276259
260+ fn check_identifier (
261+ ident : & oxc_ast:: ast:: IdentifierReference < ' _ > ,
262+ source : & str ,
263+ detected : & mut Vec < DetectedApi > ,
264+ ) {
265+ // Standalone identifiers: __dirname, __filename, Buffer (as global)
266+ let name = ident. name . as_str ( ) ;
267+ match name {
268+ "__dirname" | "__filename" => {
269+ let line = line_number_at_offset ( source, ident. span . start ) ;
270+ detected. push ( DetectedApi {
271+ pattern : name. to_string ( ) ,
272+ line,
273+ compatibility : Compatibility :: NeedsManualAttention ,
274+ } ) ;
275+ }
276+ "Buffer" => {
277+ let line = line_number_at_offset ( source, ident. span . start ) ;
278+ detected. push ( DetectedApi {
279+ pattern : "Buffer (global)" . into ( ) ,
280+ line,
281+ compatibility : Compatibility :: NeedsManualAttention ,
282+ } ) ;
283+ }
277284 _ => { }
278285 }
279286}
280287
288+ fn check_assignment_expression (
289+ assign : & oxc_ast:: ast:: AssignmentExpression < ' _ > ,
290+ source : & str ,
291+ detected : & mut Vec < DetectedApi > ,
292+ ) {
293+ // module.exports (CJS export pattern)
294+ if let Some ( member) = extract_member_pattern ( & assign. left ) {
295+ if member == "module.exports" {
296+ let line = line_number_at_offset ( source, assign. span . start ) ;
297+ detected. push ( DetectedApi {
298+ pattern : "module.exports" . into ( ) ,
299+ line,
300+ compatibility : Compatibility :: NeedsManualAttention ,
301+ } ) ;
302+ }
303+ }
304+ check_expression ( & assign. right , source, detected) ;
305+ }
281306/// Try to extract a `obj.prop` pattern string from an assignment target.
282307fn extract_member_pattern ( target : & oxc_ast:: ast:: AssignmentTarget < ' _ > ) -> Option < String > {
283308 if let oxc_ast:: ast:: AssignmentTarget :: StaticMemberExpression ( member) = target {
0 commit comments