Skip to content

Commit 6aa0940

Browse files
feat(util): add utility function to resolve binding path (#151)
Co-authored-by: Jacob Smith <[email protected]>
1 parent 6815c6e commit 6aa0940

File tree

2 files changed

+500
-0
lines changed

2 files changed

+500
-0
lines changed
Lines changed: 278 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,278 @@
1+
import assert from "node:assert/strict";
2+
import { describe, it } from "node:test";
3+
import astGrep from "@ast-grep/napi";
4+
import dedent from "dedent";
5+
6+
import { resolveBindingPath } from "./resolve-binding-path.ts";
7+
8+
describe("resolve-binding-path", () => {
9+
it("should be able to solve binding path to simple requires", () => {
10+
const code = dedent`
11+
const util = require('node:util');
12+
`;
13+
14+
const rootNode = astGrep.parse(astGrep.Lang.JavaScript, code);
15+
const importStatement = rootNode.root().find({
16+
rule: {
17+
kind: "lexical_declaration",
18+
},
19+
});
20+
21+
const bindingPath = resolveBindingPath(importStatement!, "$.types.isNativeError");
22+
23+
assert.strictEqual(bindingPath, "util.types.isNativeError");
24+
});
25+
26+
it("should be able to solve binding path when destructuring happen", () => {
27+
const code = dedent`
28+
const { types } = require('node:util');
29+
`;
30+
31+
const rootNode = astGrep.parse(astGrep.Lang.JavaScript, code);
32+
const importStatement = rootNode.root().find({
33+
rule: {
34+
kind: "variable_declarator",
35+
},
36+
});
37+
38+
const bindingPath = resolveBindingPath(importStatement!, "$.types.isNativeError");
39+
40+
assert.strictEqual(bindingPath, "types.isNativeError");
41+
});
42+
43+
it("should be able to solve binding when have multiple destructurings", () => {
44+
const code = dedent`
45+
const { types: { isNativeError } } = require('node:util');
46+
`;
47+
48+
const rootNode = astGrep.parse(astGrep.Lang.JavaScript, code);
49+
const requireStatement = rootNode.root().find({
50+
rule: {
51+
kind: "variable_declarator",
52+
},
53+
});
54+
55+
const bindingPath = resolveBindingPath(requireStatement!, "$.types.isNativeError");
56+
57+
assert.strictEqual(bindingPath, "isNativeError");
58+
});
59+
60+
it("should be able to solve binding when a rename happen", () => {
61+
const code = dedent`
62+
const { types: typesRenamed } = require('node:util');
63+
`;
64+
65+
const rootNode = astGrep.parse(astGrep.Lang.JavaScript, code);
66+
const importStatement = rootNode.root().find({
67+
rule: {
68+
kind: "variable_declarator",
69+
},
70+
});
71+
72+
const bindingPath = resolveBindingPath(importStatement!, "$.types.isNativeError");
73+
74+
assert.strictEqual(bindingPath, "typesRenamed.isNativeError");
75+
});
76+
77+
it("should throw an error if unsupported node kind is passed", () => {
78+
const code = dedent`
79+
function test() { }
80+
`;
81+
82+
const rootNode = astGrep.parse(astGrep.Lang.JavaScript, code);
83+
const functionDeclaration = rootNode.root().find({
84+
rule: {
85+
kind: "function_declaration",
86+
},
87+
});
88+
89+
assert.throws(() => resolveBindingPath(functionDeclaration!, "$.types.isNativeError"));
90+
});
91+
92+
it("should be able to solve binding using esmodule with default import", () => {
93+
const code = dedent`
94+
import util from 'node:util';
95+
`;
96+
97+
const rootNode = astGrep.parse(astGrep.Lang.JavaScript, code);
98+
const importStatement = rootNode.root().find({
99+
rule: {
100+
kind: "import_statement",
101+
},
102+
});
103+
104+
const bindingPath = resolveBindingPath(importStatement!, "$.types.isNativeError");
105+
106+
assert.strictEqual(bindingPath, "util.types.isNativeError");
107+
});
108+
109+
it("should be able to solve binding using esmodule with named imports", () => {
110+
const code = dedent`
111+
import { types } from 'node:util';
112+
`;
113+
114+
const rootNode = astGrep.parse(astGrep.Lang.JavaScript, code);
115+
const importStatement = rootNode.root().find({
116+
rule: {
117+
kind: "import_statement",
118+
},
119+
});
120+
121+
const bindingPath = resolveBindingPath(importStatement!, "$.types.isNativeError");
122+
123+
assert.strictEqual(bindingPath, "types.isNativeError");
124+
});
125+
126+
it("should be able to solve binding using esmodule with named imports using alias", () => {
127+
const code = dedent`
128+
import { types as renamedTypes } from 'node:util';
129+
`;
130+
131+
const rootNode = astGrep.parse(astGrep.Lang.JavaScript, code);
132+
const importStatement = rootNode.root().find({
133+
rule: {
134+
kind: "import_statement",
135+
},
136+
});
137+
138+
const bindingPath = resolveBindingPath(importStatement!, "$.types.isNativeError");
139+
140+
assert.strictEqual(bindingPath, "renamedTypes.isNativeError");
141+
});
142+
143+
it("should be able to solve binding using esmodule with namespace import", () => {
144+
const code = dedent`
145+
import * as example from 'node:util';
146+
`;
147+
148+
const rootNode = astGrep.parse(astGrep.Lang.JavaScript, code);
149+
const importStatement = rootNode.root().find({
150+
rule: {
151+
kind: "import_statement",
152+
},
153+
});
154+
155+
const bindingPath = resolveBindingPath(importStatement!, "$.types.isNativeError");
156+
157+
assert.strictEqual(bindingPath, "example.types.isNativeError");
158+
});
159+
160+
it("should handle deep nested destructuring with multiple levels", () => {
161+
const code = dedent`
162+
const { types: { isNativeError: nativeErrorCheck } } = require('node:util');
163+
`;
164+
165+
const rootNode = astGrep.parse(astGrep.Lang.JavaScript, code);
166+
const requireStatement = rootNode.root().find({
167+
rule: {
168+
kind: "variable_declarator",
169+
},
170+
});
171+
172+
const bindingPath = resolveBindingPath(requireStatement!, "$.types.isNativeError");
173+
174+
assert.strictEqual(bindingPath, "nativeErrorCheck");
175+
});
176+
177+
it("generateshould handle complex path resolution with longer dotted paths", () => {
178+
const code = dedent`
179+
const util = require('node:util');
180+
`;
181+
182+
const rootNode = astGrep.parse(astGrep.Lang.JavaScript, code);
183+
const importStatement = rootNode.root().find({
184+
rule: {
185+
kind: "lexical_declaration",
186+
},
187+
});
188+
189+
const bindingPath = resolveBindingPath(importStatement!, "$.types.format.inspect.custom");
190+
191+
assert.strictEqual(bindingPath, "util.types.format.inspect.custom");
192+
});
193+
194+
it("should handle multiple named imports with different aliases", () => {
195+
const code = dedent`
196+
import { types as utilTypes, format as utilFormat } from 'node:util';
197+
`;
198+
199+
const rootNode = astGrep.parse(astGrep.Lang.JavaScript, code);
200+
const importStatement = rootNode.root().find({
201+
rule: {
202+
kind: "import_statement",
203+
},
204+
});
205+
206+
const bindingPath = resolveBindingPath(importStatement!, "$.format.inspect");
207+
208+
assert.strictEqual(bindingPath, "utilFormat.inspect");
209+
});
210+
211+
it("should handle require with complex destructuring and renaming", () => {
212+
const code = dedent`
213+
const { types: renamed, format: { inspect } } = require('node:util');
214+
`;
215+
216+
const rootNode = astGrep.parse(astGrep.Lang.JavaScript, code);
217+
const requireStatement = rootNode.root().find({
218+
rule: {
219+
kind: "variable_declarator",
220+
},
221+
});
222+
223+
const bindingPath = resolveBindingPath(requireStatement!, "$.types.isNativeError");
224+
225+
assert.strictEqual(bindingPath, "renamed.isNativeError");
226+
});
227+
228+
it("should handle deep destructuring and return remaining path after resolved binding", () => {
229+
const code = dedent`
230+
const { a: {b: { c: { d }} } } = require('node:util');
231+
`;
232+
233+
const rootNode = astGrep.parse(astGrep.Lang.JavaScript, code);
234+
const requireStatement = rootNode.root().find({
235+
rule: {
236+
kind: "variable_declarator",
237+
},
238+
});
239+
240+
const bindingPath = resolveBindingPath(requireStatement!, "$.a.b.c.d.e");
241+
242+
assert.strictEqual(bindingPath, "d.e");
243+
});
244+
245+
it("should handle single character variable names", () => {
246+
const code = dedent`
247+
const { types: t } = require('node:util');
248+
`;
249+
250+
const rootNode = astGrep.parse(astGrep.Lang.JavaScript, code);
251+
const requireStatement = rootNode.root().find({
252+
rule: {
253+
kind: "variable_declarator",
254+
},
255+
});
256+
257+
const bindingPath = resolveBindingPath(requireStatement!, "$.types.isNativeError");
258+
259+
assert.strictEqual(bindingPath, "t.isNativeError");
260+
});
261+
262+
it("should handle mixed require patterns with array destructuring context", () => {
263+
const code = dedent`
264+
const [, { types }] = [null, require('node:util')];
265+
`;
266+
267+
const rootNode = astGrep.parse(astGrep.Lang.JavaScript, code);
268+
const requireStatement = rootNode.root().find({
269+
rule: {
270+
kind: "variable_declarator",
271+
},
272+
});
273+
274+
const bindingPath = resolveBindingPath(requireStatement!, "$.types.isNativeError");
275+
276+
assert.strictEqual(bindingPath, "types.isNativeError");
277+
});
278+
});

0 commit comments

Comments
 (0)