Skip to content

Commit 47bf67a

Browse files
refracto to use AST
1 parent fabaf92 commit 47bf67a

File tree

1 file changed

+136
-17
lines changed

1 file changed

+136
-17
lines changed

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

Lines changed: 136 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -169,35 +169,154 @@ export default function transform(root: SgRoot<Js>): string | null {
169169
/**
170170
* Helper function to determine if a parameter is likely a file descriptor
171171
* rather than a file path string.
172-
* @todo(@AugustinMauroy): use more AST than regex for this function.
173172
* @param param The parameter to check (e.g., 'fd').
174173
* @param rootNode The root node of the AST to search within.
175174
*/
176175
function isLikelyFileDescriptor(param: string, rootNode: SgNode<Js>): boolean {
177176
// Check if it's a numeric literal
178177
if (/^\d+$/.test(param.trim())) return true;
179178

180-
// Simple check: if the parameter appears in an `openSync` assignment, it's likely a file descriptor
181-
const sourceText = rootNode.text();
182-
183-
// Look for patterns like "const fd = openSync(...)" or "const fd = fs.openSync(...)"
184-
const openSyncPattern = new RegExp(`(?:const|let|var)\\s+${param}\\s*=\\s*(?:fs\\.)?openSync\\s*\\(`, 'g');
185-
186-
if (openSyncPattern.test(sourceText)) return true;
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({
182+
rule: {
183+
kind: "variable_declarator",
184+
all: [
185+
{
186+
has: {
187+
field: "name",
188+
kind: "identifier",
189+
regex: `^${param}$`
190+
}
191+
},
192+
{
193+
has: {
194+
field: "value",
195+
kind: "call_expression",
196+
has: {
197+
field: "function",
198+
kind: "member_expression",
199+
all: [
200+
{
201+
has: {
202+
field: "object",
203+
kind: "identifier",
204+
regex: "^fs$"
205+
}
206+
},
207+
{
208+
has: {
209+
field: "property",
210+
kind: "property_identifier",
211+
regex: "^(open|openSync)$"
212+
}
213+
}
214+
]
215+
}
216+
}
217+
}
218+
]
219+
}
220+
});
187221

188-
// Look for patterns where the parameter is used in an open callback
189-
// This handles cases like: open('file', (err, fd) => { truncate(fd, ...) })
190-
const callbackPattern = new RegExp(`\\(\\s*(?:err|error)\\s*,\\s*${param}\\s*\\)\\s*=>`, 'g');
222+
if (variableDeclarators.length > 0) return true;
191223

192-
if (callbackPattern.test(sourceText)) return true;
224+
// Check if the parameter appears as a callback parameter in fs.open calls
225+
// Pattern: open(..., (err, fd) => ...)
226+
const callbackParameters = rootNode.findAll({
227+
rule: {
228+
kind: "call_expression",
229+
all: [
230+
{
231+
has: {
232+
field: "function",
233+
kind: "identifier",
234+
regex: "^open$"
235+
}
236+
},
237+
{
238+
has: {
239+
field: "arguments",
240+
kind: "arguments",
241+
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+
}
254+
}
255+
}
256+
}
257+
}
258+
}
259+
]
260+
}
261+
});
193262

263+
if (callbackParameters.length > 0) return true;
194264

195-
// Look for function callback patterns
196-
const functionCallbackPattern = new RegExp(`function\\s*\\(\\s*(?:err|error)\\s*,\\s*${param}\\s*\\)`, 'g');
265+
// Check for fs.open callback patterns as well
266+
const fsOpenCallbacks = rootNode.findAll({
267+
rule: {
268+
kind: "call_expression",
269+
all: [
270+
{
271+
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+
]
290+
}
291+
},
292+
{
293+
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+
}
312+
}
313+
}
314+
]
315+
}
316+
});
197317

198-
if (functionCallbackPattern.test(sourceText)) return true;
318+
if (fsOpenCallbacks.length > 0) return true;
199319

200-
// Conservative approach: if we can't determine it's a file descriptor,
201-
// assume it's a file path to avoid breaking valid path-based truncate calls
320+
// If we didn't find any indicators, assume it's not a file descriptor
202321
return false;
203322
}

0 commit comments

Comments
 (0)