Skip to content

Commit a0386d1

Browse files
authored
feat(utils/import-statement): add support to dynamic import with promise (#189)
1 parent 0a41d52 commit a0386d1

File tree

2 files changed

+274
-57
lines changed

2 files changed

+274
-57
lines changed

utils/src/ast-grep/import-statement.test.ts

Lines changed: 140 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
1-
import assert from "node:assert/strict";
2-
import { describe, it } from "node:test";
1+
import assert from 'node:assert/strict';
2+
import { describe, it } from 'node:test';
33
import astGrep from '@ast-grep/napi';
44
import dedent from 'dedent';
5-
import { getNodeImportStatements, getNodeImportCalls } from "./import-statement.ts";
5+
import {
6+
getNodeImportStatements,
7+
getNodeImportCalls,
8+
} from './import-statement.ts';
69

7-
describe("import-statement", () => {
8-
it("should return import statements", () => {
10+
describe('import-statement', () => {
11+
it('should return import statements', () => {
912
const code = dedent`
1013
import fs from 'fs';
1114
import { join } from 'node:path';
@@ -31,14 +34,17 @@ describe("import-statement", () => {
3134

3235
const childProcessImports = getNodeImportStatements(ast, 'child_process');
3336
assert.strictEqual(childProcessImports.length, 1);
34-
assert.strictEqual(childProcessImports[0].field('source')?.text(), '"child_process"');
37+
assert.strictEqual(
38+
childProcessImports[0].field('source')?.text(),
39+
'"child_process"',
40+
);
3541

3642
const utilImports = getNodeImportStatements(ast, 'util');
3743
assert.strictEqual(utilImports.length, 1);
3844
assert.strictEqual(utilImports[0].field('source')?.text(), '"node:util"');
3945
});
4046

41-
it("should return import calls", () => {
47+
it('should return import calls', () => {
4248
const code = dedent`
4349
const fs = await import('fs');
4450
var { join } = await import('node:path');
@@ -57,25 +63,54 @@ describe("import-statement", () => {
5763
assert.strictEqual(fsCalls.length, 1);
5864
const fsCallExpr = fsCalls[0].field('value')?.children()[1]; // await_expression -> call_expression
5965
assert.strictEqual(fsCallExpr?.field('function')?.text(), 'import');
60-
assert.strictEqual(fsCallExpr?.field('arguments')?.find({ rule: { kind: "string" } })?.text(), "'fs'");
66+
assert.strictEqual(
67+
fsCallExpr
68+
?.field('arguments')
69+
?.find({ rule: { kind: 'string' } })
70+
?.text(),
71+
"'fs'",
72+
);
6173

6274
const pathCalls = getNodeImportCalls(ast, 'path');
6375
assert.strictEqual(pathCalls.length, 1);
6476
const pathCallExpr = pathCalls[0].field('value')?.children()[1]; // await_expression -> call_expression
6577
assert.strictEqual(pathCallExpr?.field('function')?.text(), 'import');
66-
assert.strictEqual(pathCallExpr?.field('arguments')?.find({ rule: { kind: "string" } })?.text(), "'node:path'");
78+
assert.strictEqual(
79+
pathCallExpr
80+
?.field('arguments')
81+
?.find({ rule: { kind: 'string' } })
82+
?.text(),
83+
"'node:path'",
84+
);
6785

6886
const childProcessCalls = getNodeImportCalls(ast, 'child_process');
6987
assert.strictEqual(childProcessCalls.length, 1);
70-
const childProcessCallExpr = childProcessCalls[0].field('value')?.children()[1]; // await_expression -> call_expression
71-
assert.strictEqual(childProcessCallExpr?.field('function')?.text(), 'import');
72-
assert.strictEqual(childProcessCallExpr?.field('arguments')?.find({ rule: { kind: "string" } })?.text(), '"child_process"');
88+
const childProcessCallExpr = childProcessCalls[0]
89+
.field('value')
90+
?.children()[1]; // await_expression -> call_expression
91+
assert.strictEqual(
92+
childProcessCallExpr?.field('function')?.text(),
93+
'import',
94+
);
95+
assert.strictEqual(
96+
childProcessCallExpr
97+
?.field('arguments')
98+
?.find({ rule: { kind: 'string' } })
99+
?.text(),
100+
'"child_process"',
101+
);
73102

74103
const utilCalls = getNodeImportCalls(ast, 'util');
75104
assert.strictEqual(utilCalls.length, 1);
76105
const utilCallExpr = utilCalls[0].field('value')?.children()[1]; // await_expression -> call_expression
77106
assert.strictEqual(utilCallExpr?.field('function')?.text(), 'import');
78-
assert.strictEqual(utilCallExpr?.field('arguments')?.find({ rule: { kind: "string" } })?.text(), '"node:util"');
107+
assert.strictEqual(
108+
utilCallExpr
109+
?.field('arguments')
110+
?.find({ rule: { kind: 'string' } })
111+
?.text(),
112+
'"node:util"',
113+
);
79114
});
80115

81116
it("shouldn't catch pending promises during import calls", () => {
@@ -85,6 +120,96 @@ describe("import-statement", () => {
85120
const ast = astGrep.parse(astGrep.Lang.JavaScript, code);
86121

87122
const moduleCalls = getNodeImportCalls(ast, 'module');
88-
assert.strictEqual(moduleCalls.length, 0, "Pending import calls should not be caught");
123+
assert.strictEqual(
124+
moduleCalls.length,
125+
0,
126+
'Pending import calls should not be caught',
127+
);
89128
});
90-
})
129+
130+
it('should catch thenable during import calls', () => {
131+
const code = dedent`
132+
import("node:fs").then((mdl) => {
133+
const readFile = mdl.readFile;
134+
135+
readFile("package.json", "utf8", (err, data) => {
136+
if (err) throw err;
137+
console.log({ data });
138+
});
139+
});
140+
`;
141+
const ast = astGrep.parse(astGrep.Lang.JavaScript, code);
142+
143+
const moduleCalls = getNodeImportCalls(ast, 'fs');
144+
assert.strictEqual(
145+
moduleCalls.length,
146+
1,
147+
'thenable import calls should be caught',
148+
);
149+
});
150+
151+
it('should catch thenable during import calls with catch block', () => {
152+
const code = dedent`
153+
import("node:fs").then((mdl) => {
154+
const readFile = mdl.readFile;
155+
156+
readFile("package.json", "utf8", (err, data) => {
157+
if (err) throw err;
158+
console.log({ data });
159+
});
160+
}).catch(console.log);
161+
`;
162+
const ast = astGrep.parse(astGrep.Lang.JavaScript, code);
163+
164+
const moduleCalls = getNodeImportCalls(ast, 'fs');
165+
assert.strictEqual(
166+
moduleCalls.length,
167+
1,
168+
'thenable import calls should be caught',
169+
);
170+
});
171+
172+
it("shouldn't catch when there is no 'then'", () => {
173+
const code = dedent`
174+
import("node:fs").catch(console.log);
175+
`;
176+
const ast = astGrep.parse(astGrep.Lang.JavaScript, code);
177+
178+
const moduleCalls = getNodeImportCalls(ast, 'fs');
179+
assert.strictEqual(
180+
moduleCalls.length,
181+
0,
182+
"dynamic import without then shouldn't be caught",
183+
);
184+
});
185+
186+
it('should get variable declarator with module', () => {
187+
const code = dedent`
188+
const variable = 'node:fs'
189+
import(variable).then(console.log);
190+
`;
191+
const ast = astGrep.parse(astGrep.Lang.JavaScript, code);
192+
193+
const moduleCalls = getNodeImportCalls(ast, 'fs');
194+
assert.strictEqual(
195+
moduleCalls.length,
196+
1,
197+
'dynamic import using variable need to be caught',
198+
);
199+
});
200+
201+
it('should not capture function calls that are not import() expressions', () => {
202+
const code = dedent`
203+
var test = anyFn("fs");
204+
var test2 = anyFn("node:fs");
205+
`;
206+
const ast = astGrep.parse(astGrep.Lang.JavaScript, code);
207+
208+
const moduleCalls = getNodeImportCalls(ast, 'fs');
209+
assert.strictEqual(
210+
moduleCalls.length,
211+
0,
212+
"functions that aren't import shouldn't be caught",
213+
);
214+
});
215+
});

0 commit comments

Comments
 (0)