@@ -176,9 +176,178 @@ function isLikelyFileDescriptor(param: string, rootNode: SgNode<Js>): boolean {
176
176
// Check if it's a numeric literal
177
177
if ( / ^ \d + $ / . test ( param . trim ( ) ) ) return true ;
178
178
179
- // Search for variable declarations that might assign a file descriptor
180
- // such as `const fd = fs.openSync(...)` or `open(..., (err, fd) => ...)`
181
- const variableDeclarators = rootNode . findAll ( {
179
+ // Check if parameter is in a callback context
180
+ if ( isInCallbackContext ( param , rootNode ) ) return true ;
181
+
182
+ // Check if there's a variable in scope that assigns a file descriptor
183
+ if ( hasFileDescriptorVariable ( param , rootNode ) ) return true ;
184
+
185
+ // If we didn't find any indicators, assume it's not a file descriptor
186
+ return false ;
187
+ }
188
+
189
+ /**
190
+ * Check if the parameter is used inside a callback context from fs.open
191
+ * @param param The parameter name to check
192
+ * @param rootNode The root node of the AST
193
+ */
194
+ function isInCallbackContext ( param : string , rootNode : SgNode < Js > ) : boolean {
195
+ // Find all uses of the parameter
196
+ const parameterUsages = rootNode . findAll ( {
197
+ rule : {
198
+ kind : "identifier" ,
199
+ regex : `^${ param } $`
200
+ }
201
+ } ) ;
202
+
203
+ for ( const usage of parameterUsages ) {
204
+ // Check if this usage is inside a callback parameter list for fs.open
205
+ const isInFsOpenCallback = usage . inside ( {
206
+ rule : {
207
+ kind : "call_expression" ,
208
+ all : [
209
+ {
210
+ has : {
211
+ field : "function" ,
212
+ kind : "member_expression" ,
213
+ all : [
214
+ {
215
+ has : {
216
+ field : "object" ,
217
+ kind : "identifier" ,
218
+ regex : "^fs$"
219
+ }
220
+ } ,
221
+ {
222
+ has : {
223
+ field : "property" ,
224
+ kind : "property_identifier" ,
225
+ regex : "^open$"
226
+ }
227
+ }
228
+ ]
229
+ }
230
+ } ,
231
+ {
232
+ has : {
233
+ field : "arguments" ,
234
+ kind : "arguments" ,
235
+ has : {
236
+ any : [
237
+ {
238
+ kind : "arrow_function" ,
239
+ has : {
240
+ field : "parameters" ,
241
+ kind : "formal_parameters" ,
242
+ has : {
243
+ // @ts -ignore - jssg-types arren't happy but jssg work with type_error
244
+ kind : "required_parameter" ,
245
+ has : {
246
+ field : "pattern" ,
247
+ kind : "identifier" ,
248
+ regex : `^${ param } $`
249
+ }
250
+ }
251
+ }
252
+ } ,
253
+ {
254
+ kind : "function_expression" ,
255
+ has : {
256
+ field : "parameters" ,
257
+ kind : "formal_parameters" ,
258
+ has : {
259
+ // @ts -ignore - jssg-types arren't happy but jssg work with type_error
260
+ kind : "required_parameter" ,
261
+ has : {
262
+ field : "pattern" ,
263
+ kind : "identifier" ,
264
+ regex : `^${ param } $`
265
+ }
266
+ }
267
+ }
268
+ }
269
+ ]
270
+ }
271
+ }
272
+ }
273
+ ]
274
+ }
275
+ } ) ;
276
+
277
+ // Check if this usage is inside a callback parameter list for destructured open
278
+ const isInDestructuredOpenCallback = usage . inside ( {
279
+ rule : {
280
+ kind : "call_expression" ,
281
+ all : [
282
+ {
283
+ has : {
284
+ field : "function" ,
285
+ kind : "identifier" ,
286
+ regex : "^open$"
287
+ }
288
+ } ,
289
+ {
290
+ has : {
291
+ field : "arguments" ,
292
+ kind : "arguments" ,
293
+ has : {
294
+ any : [
295
+ {
296
+ kind : "arrow_function" ,
297
+ has : {
298
+ field : "parameters" ,
299
+ kind : "formal_parameters" ,
300
+ has : {
301
+ // @ts -ignore - jssg-types arren't happy but jssg work with type_error
302
+ kind : "required_parameter" ,
303
+ has : {
304
+ field : "pattern" ,
305
+ kind : "identifier" ,
306
+ regex : `^${ param } $`
307
+ }
308
+ }
309
+ }
310
+ } ,
311
+ {
312
+ kind : "function_expression" ,
313
+ has : {
314
+ field : "parameters" ,
315
+ kind : "formal_parameters" ,
316
+ has : {
317
+ // @ts -ignore - jssg-types arren't happy but jssg work with type_error
318
+ kind : "required_parameter" ,
319
+ has : {
320
+ field : "pattern" ,
321
+ kind : "identifier" ,
322
+ regex : `^${ param } $`
323
+ }
324
+ }
325
+ }
326
+ }
327
+ ]
328
+ }
329
+ }
330
+ }
331
+ ]
332
+ }
333
+ } ) ;
334
+
335
+ if ( isInFsOpenCallback || isInDestructuredOpenCallback ) {
336
+ return true ;
337
+ }
338
+ }
339
+
340
+ return false ;
341
+ }
342
+
343
+ /**
344
+ * Check if there's a variable in scope that assigns a file descriptor value
345
+ * @param param The parameter name to check
346
+ * @param rootNode The root node of the AST
347
+ */
348
+ function hasFileDescriptorVariable ( param : string , rootNode : SgNode < Js > ) : boolean {
349
+ // Search for variable declarations that assign from fs.openSync
350
+ const syncVariableDeclarators = rootNode . findAll ( {
182
351
rule : {
183
352
kind : "variable_declarator" ,
184
353
all : [
@@ -195,21 +364,29 @@ function isLikelyFileDescriptor(param: string, rootNode: SgNode<Js>): boolean {
195
364
kind : "call_expression" ,
196
365
has : {
197
366
field : "function" ,
198
- kind : "member_expression" ,
199
- all : [
367
+ any : [
200
368
{
201
- has : {
202
- field : "object" ,
203
- kind : "identifier" ,
204
- regex : "^fs$"
205
- }
369
+ kind : "member_expression" ,
370
+ all : [
371
+ {
372
+ has : {
373
+ field : "object" ,
374
+ kind : "identifier" ,
375
+ regex : "^fs$"
376
+ }
377
+ } ,
378
+ {
379
+ has : {
380
+ field : "property" ,
381
+ kind : "property_identifier" ,
382
+ regex : "^openSync$"
383
+ }
384
+ }
385
+ ]
206
386
} ,
207
387
{
208
- has : {
209
- field : "property" ,
210
- kind : "property_identifier" ,
211
- regex : "^(open|openSync)$"
212
- }
388
+ kind : "identifier" ,
389
+ regex : "^openSync$"
213
390
}
214
391
]
215
392
}
@@ -219,104 +396,92 @@ function isLikelyFileDescriptor(param: string, rootNode: SgNode<Js>): boolean {
219
396
}
220
397
} ) ;
221
398
222
- if ( variableDeclarators . length > 0 ) return true ;
399
+ if ( syncVariableDeclarators . length > 0 ) return true ;
223
400
224
- // Check if the parameter appears as a callback parameter in fs.open calls
225
- // Pattern: open(..., (err, fd) => ...)
226
- const callbackParameters = rootNode . findAll ( {
401
+ // Search for assignment expressions that assign from fs.openSync
402
+ const syncAssignments = rootNode . findAll ( {
227
403
rule : {
228
- kind : "call_expression " ,
404
+ kind : "assignment_expression " ,
229
405
all : [
230
406
{
231
407
has : {
232
- field : "function " ,
408
+ field : "left " ,
233
409
kind : "identifier" ,
234
- regex : "^open$"
410
+ regex : `^ ${ param } $`
235
411
}
236
412
} ,
237
413
{
238
414
has : {
239
- field : "arguments " ,
240
- kind : "arguments " ,
415
+ field : "right " ,
416
+ kind : "call_expression " ,
241
417
has : {
242
- kind : "arrow_function" ,
243
- has : {
244
- field : "parameters" ,
245
- kind : "formal_parameters" ,
246
- has : {
247
- // @ts -ignore - idk what happend here maybe a bug in infering `Js` type
248
- kind : "required_parameter" ,
249
- has : {
250
- field : "pattern" ,
251
- kind : "identifier" ,
252
- regex : `^${ param } $`
253
- }
418
+ field : "function" ,
419
+ any : [
420
+ {
421
+ kind : "member_expression" ,
422
+ all : [
423
+ {
424
+ has : {
425
+ field : "object" ,
426
+ kind : "identifier" ,
427
+ regex : "^fs$"
428
+ }
429
+ } ,
430
+ {
431
+ has : {
432
+ field : "property" ,
433
+ kind : "property_identifier" ,
434
+ regex : "^openSync$"
435
+ }
436
+ }
437
+ ]
438
+ } ,
439
+ {
440
+ kind : "identifier" ,
441
+ regex : "^openSync$"
254
442
}
255
- }
443
+ ]
256
444
}
257
445
}
258
446
}
259
447
]
260
448
}
261
449
} ) ;
262
450
263
- if ( callbackParameters . length > 0 ) return true ;
451
+ if ( syncAssignments . length > 0 ) return true ;
264
452
265
- // Check for fs.open callback patterns as well
266
- const fsOpenCallbacks = rootNode . findAll ( {
453
+ // Check if the variable is assigned from another variable that's a file descriptor
454
+ const variableAssignments = rootNode . findAll ( {
267
455
rule : {
268
- kind : "call_expression " ,
456
+ kind : "variable_declarator " ,
269
457
all : [
270
458
{
271
459
has : {
272
- field : "function" ,
273
- kind : "member_expression" ,
274
- all : [
275
- {
276
- has : {
277
- field : "object" ,
278
- kind : "identifier" ,
279
- regex : "^fs$"
280
- }
281
- } ,
282
- {
283
- has : {
284
- field : "property" ,
285
- kind : "property_identifier" ,
286
- regex : "^open$"
287
- }
288
- }
289
- ]
460
+ field : "name" ,
461
+ kind : "identifier" ,
462
+ regex : `^${ param } $`
290
463
}
291
464
} ,
292
465
{
293
466
has : {
294
- field : "arguments" ,
295
- kind : "arguments" ,
296
- has : {
297
- kind : "arrow_function" ,
298
- has : {
299
- field : "parameters" ,
300
- kind : "formal_parameters" ,
301
- has : {
302
- // @ts -ignore - idk what happend here maybe a bug in infering `Js` type
303
- kind : "required_parameter" ,
304
- has : {
305
- field : "pattern" ,
306
- kind : "identifier" ,
307
- regex : `^${ param } $`
308
- }
309
- }
310
- }
311
- }
467
+ field : "value" ,
468
+ kind : "identifier"
312
469
}
313
470
}
314
471
]
315
472
}
316
473
} ) ;
317
474
318
- if ( fsOpenCallbacks . length > 0 ) return true ;
475
+ for ( const assignment of variableAssignments ) {
476
+ const valueNode = assignment . field ( "value" ) ;
477
+ if ( valueNode ) {
478
+ const sourceVar = valueNode . text ( ) ;
479
+ // Recursively check if the source variable is a file descriptor
480
+ if ( hasFileDescriptorVariable ( sourceVar , rootNode ) ) {
481
+ return true ;
482
+ }
483
+ }
484
+ }
319
485
320
- // If we didn't find any indicators, assume it's not a file descriptor
321
486
return false ;
322
487
}
0 commit comments