@@ -346,8 +346,77 @@ function isInCallbackContext(param: string, rootNode: SgNode<Js>): boolean {
346
346
* @param rootNode The root node of the AST
347
347
*/
348
348
function hasFileDescriptorVariable ( param : string , rootNode : SgNode < Js > ) : boolean {
349
- // Search for variable declarations that assign from fs.openSync
350
- const syncVariableDeclarators = rootNode . findAll ( {
349
+ // Find all usages of the parameter to understand the context
350
+ const parameterUsages = rootNode . findAll ( {
351
+ rule : {
352
+ kind : "identifier" ,
353
+ regex : `^${ param } $`
354
+ }
355
+ } ) ;
356
+
357
+ // For each usage, check if there's a variable declaration in scope
358
+ for ( const usage of parameterUsages ) {
359
+ // Check if this usage is in a truncate call context
360
+ const isInTruncateCall = usage . inside ( {
361
+ rule : {
362
+ any : [
363
+ { pattern : "fs.truncate($FD, $LEN, $CALLBACK)" } ,
364
+ { pattern : "fs.truncate($FD, $LEN)" } ,
365
+ { pattern : "truncate($FD, $LEN, $CALLBACK)" } ,
366
+ { pattern : "truncate($FD, $LEN)" } ,
367
+ { pattern : "fs.truncateSync($FD, $LEN)" } ,
368
+ { pattern : "truncateSync($FD, $LEN)" }
369
+ ]
370
+ }
371
+ } ) ;
372
+
373
+ if ( ! isInTruncateCall ) continue ;
374
+
375
+ // Find the scope containing this usage
376
+ const scope = findContainingScope ( usage ) ;
377
+ if ( ! scope ) continue ;
378
+
379
+ // Search for variable declarations within this scope
380
+ if ( hasFileDescriptorVariableInScope ( param , scope ) ) {
381
+ return true ;
382
+ }
383
+ }
384
+
385
+ return false ;
386
+ }
387
+
388
+ /**
389
+ * Find the containing scope (function, block, or program) for a given node
390
+ * @param node The node to find the scope for
391
+ */
392
+ function findContainingScope ( node : SgNode < Js > ) : SgNode < Js > | null {
393
+ let current = node . parent ( ) ;
394
+
395
+ while ( current ) {
396
+ const kind = current . kind ( ) ;
397
+ // These are scope-creating nodes in JavaScript
398
+ if ( kind === "program" ||
399
+ kind === "function_declaration" ||
400
+ kind === "function_expression" ||
401
+ kind === "arrow_function" ||
402
+ kind === "method_definition" ||
403
+ kind === "statement_block" ) {
404
+ return current ;
405
+ }
406
+ current = current . parent ( ) ;
407
+ }
408
+
409
+ return null ;
410
+ }
411
+
412
+ /**
413
+ * Check if there's a file descriptor variable declaration within a specific scope
414
+ * @param param The parameter name to check
415
+ * @param scope The scope node to search within
416
+ */
417
+ function hasFileDescriptorVariableInScope ( param : string , scope : SgNode < Js > ) : boolean {
418
+ // Search for variable declarations that assign from fs.openSync within this scope
419
+ const syncVariableDeclarators = scope . findAll ( {
351
420
rule : {
352
421
kind : "variable_declarator" ,
353
422
all : [
@@ -398,8 +467,8 @@ function hasFileDescriptorVariable(param: string, rootNode: SgNode<Js>): boolean
398
467
399
468
if ( syncVariableDeclarators . length > 0 ) return true ;
400
469
401
- // Search for assignment expressions that assign from fs.openSync
402
- const syncAssignments = rootNode . findAll ( {
470
+ // Search for assignment expressions that assign from fs.openSync within this scope
471
+ const syncAssignments = scope . findAll ( {
403
472
rule : {
404
473
kind : "assignment_expression" ,
405
474
all : [
@@ -450,8 +519,8 @@ function hasFileDescriptorVariable(param: string, rootNode: SgNode<Js>): boolean
450
519
451
520
if ( syncAssignments . length > 0 ) return true ;
452
521
453
- // Check if the variable is assigned from another variable that's a file descriptor
454
- const variableAssignments = rootNode . findAll ( {
522
+ // Check if the variable is assigned from another variable that's a file descriptor within this scope
523
+ const variableAssignments = scope . findAll ( {
455
524
rule : {
456
525
kind : "variable_declarator" ,
457
526
all : [
@@ -476,12 +545,42 @@ function hasFileDescriptorVariable(param: string, rootNode: SgNode<Js>): boolean
476
545
const valueNode = assignment . field ( "value" ) ;
477
546
if ( valueNode ) {
478
547
const sourceVar = valueNode . text ( ) ;
479
- // Recursively check if the source variable is a file descriptor
480
- if ( hasFileDescriptorVariable ( sourceVar , rootNode ) ) {
548
+ // Recursively check if the source variable is a file descriptor within the same scope
549
+ if ( hasFileDescriptorVariableInScope ( sourceVar , scope ) ) {
481
550
return true ;
482
551
}
483
552
}
484
553
}
485
554
555
+ // If not found in current scope, check parent scopes (for closure/lexical scoping)
556
+ const parentScope = findParentScope ( scope ) ;
557
+ if ( parentScope ) {
558
+ return hasFileDescriptorVariableInScope ( param , parentScope ) ;
559
+ }
560
+
486
561
return false ;
487
562
}
563
+
564
+ /**
565
+ * Find the parent scope of a given scope node
566
+ * @param scope The current scope node
567
+ */
568
+ function findParentScope ( scope : SgNode < Js > ) : SgNode < Js > | null {
569
+ let current = scope . parent ( ) ;
570
+
571
+ while ( current ) {
572
+ const kind = current . kind ( ) ;
573
+ // These are scope-creating nodes in JavaScript
574
+ if ( kind === "program" ||
575
+ kind === "function_declaration" ||
576
+ kind === "function_expression" ||
577
+ kind === "arrow_function" ||
578
+ kind === "method_definition" ||
579
+ kind === "statement_block" ) {
580
+ return current ;
581
+ }
582
+ current = current . parent ( ) ;
583
+ }
584
+
585
+ return null ;
586
+ }
0 commit comments