Skip to content

Commit 5c055ec

Browse files
feat(util/update-binding): add new utility to update binding in the file (#182)
Co-authored-by: Augustin Mauroy <[email protected]>
1 parent 6c013ab commit 5c055ec

File tree

5 files changed

+1061
-196
lines changed

5 files changed

+1061
-196
lines changed

utils/README.md

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ import { resolveBindingPath } from '@nodejs/codemod-utils';
7272

7373
#### `removeBinding(node, binding)`
7474

75-
Removes a specific binding from destructured imports/requires, or removes the entire statement if it's the only binding.
75+
Removes a specific binding from imports/requires, or removes the entire statement if it's the only binding.
7676

7777
```typescript
7878
import { removeBinding } from '@nodejs/codemod-utils';
@@ -82,6 +82,26 @@ import { removeBinding } from '@nodejs/codemod-utils';
8282

8383
// Given: const { isNativeError } = require('node:util');
8484
// removeBinding(node, 'isNativeError') → Returns line range to remove entire statement
85+
86+
// Given: const util = require('node:util');
87+
// removeBinding(node, 'util') → Returns line range to remove entire statement
88+
```
89+
90+
#### `updateBinding(node, { old, new })`
91+
92+
Updates a specific binding from imports/requires. It can be used to replace, add, or remove bindings.
93+
94+
```typescript
95+
import { updateBinding } from '@nodejs/codemod-utils';
96+
97+
// Given: const { isNativeError } = require('node:util');
98+
// updateBinding(node, {old: 'isNativeError', new: 'types'}) → Edit to: const { types } = require('node:util');
99+
100+
// Given: const { isNativeError } = require('node:util');
101+
// updateBinding(node, {old: undefined, new: 'types'}) → Edit to: const { isNativeError, types } = require('node:util');
102+
103+
// Given: const { isNativeError, types } = require('node:util');
104+
// updateBinding(node, {old: isNativeError, new: undefined}) → Works exactly as removeBinding util: const { types } = require('node:util');
85105
```
86106

87107
### Code Manipulation

utils/src/ast-grep/remove-binding.test.ts

Lines changed: 82 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import { describe, it } from "node:test";
33
import astGrep from "@ast-grep/napi";
44
import dedent from "dedent";
55
import { removeBinding } from "./remove-binding.ts";
6+
import type Js from "@codemod.com/jssg-types/langs/javascript";
7+
import type { SgNode } from "@codemod.com/jssg-types/main";
68

79
describe("remove-binding", () => {
810
it("should remove the entire require statement when the only imported binding is removed", () => {
@@ -11,7 +13,7 @@ describe("remove-binding", () => {
1113
`;
1214

1315
const rootNode = astGrep.parse(astGrep.Lang.JavaScript, code);
14-
const node = rootNode.root();
16+
const node = rootNode.root() as SgNode<Js>;
1517

1618
const requireStatement = node.find({
1719
rule: {
@@ -35,7 +37,7 @@ describe("remove-binding", () => {
3537
`;
3638

3739
const rootNode = astGrep.parse(astGrep.Lang.JavaScript, code);
38-
const node = rootNode.root();
40+
const node = rootNode.root() as SgNode<Js>;
3941

4042
const requireStatement = node.find({
4143
rule: {
@@ -55,7 +57,7 @@ describe("remove-binding", () => {
5557
`;
5658

5759
const rootNode = astGrep.parse(astGrep.Lang.JavaScript, code);
58-
const node = rootNode.root();
60+
const node = rootNode.root() as SgNode<Js>;
5961

6062
const importStatement = node.find({
6163
rule: {
@@ -79,7 +81,7 @@ describe("remove-binding", () => {
7981
`;
8082

8183
const rootNode = astGrep.parse(astGrep.Lang.JavaScript, code);
82-
const node = rootNode.root();
84+
const node = rootNode.root() as SgNode<Js>;
8385

8486
const requireStatement = node.find({
8587
rule: {
@@ -101,7 +103,7 @@ describe("remove-binding", () => {
101103
`;
102104

103105
const rootNode = astGrep.parse(astGrep.Lang.JavaScript, code);
104-
const node = rootNode.root();
106+
const node = rootNode.root() as SgNode<Js>;
105107

106108
const requireStatement = node.find({
107109
rule: {
@@ -125,7 +127,7 @@ describe("remove-binding", () => {
125127
`;
126128

127129
const rootNode = astGrep.parse(astGrep.Lang.JavaScript, code);
128-
const node = rootNode.root();
130+
const node = rootNode.root() as SgNode<Js>;
129131

130132
const importStatement = node.find({
131133
rule: {
@@ -149,7 +151,7 @@ describe("remove-binding", () => {
149151
`;
150152

151153
const rootNode = astGrep.parse(astGrep.Lang.JavaScript, code);
152-
const node = rootNode.root();
154+
const node = rootNode.root() as SgNode<Js>;
153155

154156
const importStatement = node.find({
155157
rule: {
@@ -169,7 +171,7 @@ describe("remove-binding", () => {
169171
`;
170172

171173
const rootNode = astGrep.parse(astGrep.Lang.JavaScript, code);
172-
const node = rootNode.root();
174+
const node = rootNode.root() as SgNode<Js>;
173175

174176
const importStatement = node.find({
175177
rule: {
@@ -193,7 +195,7 @@ describe("remove-binding", () => {
193195
`;
194196

195197
const rootNode = astGrep.parse(astGrep.Lang.JavaScript, code);
196-
const node = rootNode.root();
198+
const node = rootNode.root() as SgNode<Js>;
197199

198200
const importStatement = node.find({
199201
rule: {
@@ -212,7 +214,7 @@ describe("remove-binding", () => {
212214
`;
213215

214216
const rootNode = astGrep.parse(astGrep.Lang.JavaScript, code);
215-
const node = rootNode.root();
217+
const node = rootNode.root() as SgNode<Js>;
216218

217219
const importStatement = node.find({
218220
rule: {
@@ -236,7 +238,7 @@ describe("remove-binding", () => {
236238
`;
237239

238240
const rootNode = astGrep.parse(astGrep.Lang.JavaScript, code);
239-
const node = rootNode.root();
241+
const node = rootNode.root() as SgNode<Js>;
240242

241243
const importStatement = node.find({
242244
rule: {
@@ -258,7 +260,7 @@ describe("remove-binding", () => {
258260
`;
259261

260262
const rootNode = astGrep.parse(astGrep.Lang.JavaScript, code);
261-
const node = rootNode.root();
263+
const node = rootNode.root() as SgNode<Js>;
262264

263265
const importStatement = node.find({
264266
rule: {
@@ -277,7 +279,7 @@ describe("remove-binding", () => {
277279
`;
278280

279281
const rootNode = astGrep.parse(astGrep.Lang.JavaScript, code);
280-
const node = rootNode.root();
282+
const node = rootNode.root() as SgNode<Js>;
281283

282284
const importStatement = node.find({
283285
rule: {
@@ -301,7 +303,7 @@ describe("remove-binding", () => {
301303
`;
302304

303305
const rootNode = astGrep.parse(astGrep.Lang.JavaScript, code);
304-
const node = rootNode.root();
306+
const node = rootNode.root() as SgNode<Js>;
305307

306308
const importStatement = node.find({
307309
rule: {
@@ -323,7 +325,7 @@ describe("remove-binding", () => {
323325
`;
324326

325327
const rootNode = astGrep.parse(astGrep.Lang.JavaScript, code);
326-
const node = rootNode.root();
328+
const node = rootNode.root() as SgNode<Js>;
327329

328330
const importStatement = node.find({
329331
rule: {
@@ -345,7 +347,7 @@ describe("remove-binding", () => {
345347
`;
346348

347349
const rootNode = astGrep.parse(astGrep.Lang.JavaScript, code);
348-
const node = rootNode.root();
350+
const node = rootNode.root() as SgNode<Js>;
349351

350352
const importStatement = node.find({
351353
rule: {
@@ -360,4 +362,68 @@ describe("remove-binding", () => {
360362
assert.strictEqual(change?.lineToRemove, undefined);
361363
assert.strictEqual(sourceCode, `const { types: { isMap } } = require("util");`);
362364
});
365+
366+
it("Should remove the line in member expression scenarios", () => {
367+
const code = dedent`
368+
const Buffer = require("buffer").Buffer;
369+
`;
370+
371+
const rootNode = astGrep.parse(astGrep.Lang.JavaScript, code);
372+
const node = rootNode.root() as SgNode<Js>;
373+
374+
const importStatement = node.find({
375+
rule: {
376+
kind: "lexical_declaration",
377+
},
378+
});
379+
380+
const change = removeBinding(importStatement!, "Buffer");
381+
382+
assert.notEqual(change, undefined);
383+
assert.strictEqual(change?.edit, undefined);
384+
assert.deepEqual(change?.lineToRemove, {
385+
end: {
386+
column: 40,
387+
index: 40,
388+
line: 0,
389+
},
390+
start: {
391+
column: 0,
392+
index: 0,
393+
line: 0,
394+
},
395+
});
396+
});
397+
398+
it("Should remove the line when the accessed property is different from the identifier", () => {
399+
const code = dedent`
400+
const Buffer = require("buffer").SlowBuffer
401+
`;
402+
403+
const rootNode = astGrep.parse(astGrep.Lang.JavaScript, code);
404+
const node = rootNode.root() as SgNode<Js>;
405+
406+
const importStatement = node.find({
407+
rule: {
408+
kind: "lexical_declaration",
409+
},
410+
});
411+
412+
const change = removeBinding(importStatement!, "Buffer");
413+
414+
assert.notEqual(change, undefined);
415+
assert.strictEqual(change?.edit, undefined);
416+
assert.deepEqual(change?.lineToRemove, {
417+
end: {
418+
column: 43,
419+
index: 43,
420+
line: 0,
421+
},
422+
start: {
423+
column: 0,
424+
index: 0,
425+
line: 0,
426+
},
427+
});
428+
});
363429
});

0 commit comments

Comments
 (0)