Skip to content

Commit a4d4938

Browse files
refracto
1 parent 9e0365a commit a4d4938

File tree

1 file changed

+244
-79
lines changed

1 file changed

+244
-79
lines changed

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

Lines changed: 244 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -176,9 +176,178 @@ function isLikelyFileDescriptor(param: string, rootNode: SgNode<Js>): boolean {
176176
// Check if it's a numeric literal
177177
if (/^\d+$/.test(param.trim())) return true;
178178

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({
182351
rule: {
183352
kind: "variable_declarator",
184353
all: [
@@ -195,21 +364,29 @@ function isLikelyFileDescriptor(param: string, rootNode: SgNode<Js>): boolean {
195364
kind: "call_expression",
196365
has: {
197366
field: "function",
198-
kind: "member_expression",
199-
all: [
367+
any: [
200368
{
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+
]
206386
},
207387
{
208-
has: {
209-
field: "property",
210-
kind: "property_identifier",
211-
regex: "^(open|openSync)$"
212-
}
388+
kind: "identifier",
389+
regex: "^openSync$"
213390
}
214391
]
215392
}
@@ -219,104 +396,92 @@ function isLikelyFileDescriptor(param: string, rootNode: SgNode<Js>): boolean {
219396
}
220397
});
221398

222-
if (variableDeclarators.length > 0) return true;
399+
if (syncVariableDeclarators.length > 0) return true;
223400

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({
227403
rule: {
228-
kind: "call_expression",
404+
kind: "assignment_expression",
229405
all: [
230406
{
231407
has: {
232-
field: "function",
408+
field: "left",
233409
kind: "identifier",
234-
regex: "^open$"
410+
regex: `^${param}$`
235411
}
236412
},
237413
{
238414
has: {
239-
field: "arguments",
240-
kind: "arguments",
415+
field: "right",
416+
kind: "call_expression",
241417
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$"
254442
}
255-
}
443+
]
256444
}
257445
}
258446
}
259447
]
260448
}
261449
});
262450

263-
if (callbackParameters.length > 0) return true;
451+
if (syncAssignments.length > 0) return true;
264452

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({
267455
rule: {
268-
kind: "call_expression",
456+
kind: "variable_declarator",
269457
all: [
270458
{
271459
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}$`
290463
}
291464
},
292465
{
293466
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"
312469
}
313470
}
314471
]
315472
}
316473
});
317474

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+
}
319485

320-
// If we didn't find any indicators, assume it's not a file descriptor
321486
return false;
322487
}

0 commit comments

Comments
 (0)