Skip to content

Commit 13c2ddc

Browse files
WIP
1 parent aa4a23d commit 13c2ddc

File tree

3 files changed

+156
-57
lines changed

3 files changed

+156
-57
lines changed

package-lock.json

Lines changed: 48 additions & 48 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

recipes/fs-truncate-fd-deprecation/src/workflow.ts

Lines changed: 107 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -346,8 +346,77 @@ function isInCallbackContext(param: string, rootNode: SgNode<Js>): boolean {
346346
* @param rootNode The root node of the AST
347347
*/
348348
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({
351420
rule: {
352421
kind: "variable_declarator",
353422
all: [
@@ -398,8 +467,8 @@ function hasFileDescriptorVariable(param: string, rootNode: SgNode<Js>): boolean
398467

399468
if (syncVariableDeclarators.length > 0) return true;
400469

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({
403472
rule: {
404473
kind: "assignment_expression",
405474
all: [
@@ -450,8 +519,8 @@ function hasFileDescriptorVariable(param: string, rootNode: SgNode<Js>): boolean
450519

451520
if (syncAssignments.length > 0) return true;
452521

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({
455524
rule: {
456525
kind: "variable_declarator",
457526
all: [
@@ -476,12 +545,42 @@ function hasFileDescriptorVariable(param: string, rootNode: SgNode<Js>): boolean
476545
const valueNode = assignment.field("value");
477546
if (valueNode) {
478547
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)) {
481550
return true;
482551
}
483552
}
484553
}
485554

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+
486561
return false;
487562
}
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+
}

recipes/fs-truncate-fd-deprecation/tests/expected/edge-case.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ fs.ftruncateSync(accesible, 10);
1717
fs.closeSync(accesible);
1818

1919
function foo() {
20-
truncateFile(unaccessible, 10);
20+
ftruncateFile(unaccessible, 10);
2121
}
2222

2323
function bar() {

0 commit comments

Comments
 (0)