@kamaalio/codemod-kit 0.0.13 → 0.0.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -44,6 +44,34 @@ Runs a single codemod.
44
44
  - `globItems`: An array of file paths to transform.
45
45
  - `options`: Optional configuration for the run.
46
46
 
47
+ ### `findAndReplace(content, rule, transformer)`
48
+
49
+ A utility function for finding and replacing AST nodes based on a rule.
50
+
51
+ - `content`: An `SgRoot<TypesMap>` object representing the parsed AST.
52
+ - `rule`: A `Rule<TypesMap>` object defining the pattern to search for.
53
+ - `transformer`: A function that takes a matched node and returns an optional string replacement.
54
+
55
+ Returns the transformed content as a string with all matching nodes replaced.
56
+
57
+ ```typescript
58
+ import { findAndReplace } from '@kamaalio/codemod-kit';
59
+ import { parseAsync } from '@ast-grep/napi';
60
+
61
+ const code = `
62
+ function oldFunction() {
63
+ return "hello";
64
+ }
65
+ `;
66
+
67
+ const ast = await parseAsync('javascript', code);
68
+ const result = findAndReplace(
69
+ ast,
70
+ { pattern: 'function oldFunction() { $$$ }' },
71
+ node => 'function newFunction() { return "hello world"; }',
72
+ );
73
+ ```
74
+
47
75
  ### `Codemod`
48
76
 
49
77
  A codemod is defined by the `Codemod` type:
@@ -1,2 +1,2 @@
1
- export { runCodemods, runCodemod } from './utils.js';
1
+ export { runCodemods, runCodemod, commitEditModifications, findAndReplace } from './utils.js';
2
2
  export type { Codemod, Modifications } from './types.js';
@@ -1,5 +1,8 @@
1
1
  import { type Result } from 'neverthrow';
2
- import type { Codemod } from './types.js';
2
+ import { type Rule, type Edit, type SgNode, type SgRoot } from '@ast-grep/napi';
3
+ import type { Kinds, TypesMap } from '@ast-grep/napi/types/staticTypes.js';
4
+ import type { Codemod, Modifications } from './types.js';
5
+ import type { Optional } from '../utils/type-utils.js';
3
6
  type RunCodemodHooks<C extends Codemod> = {
4
7
  targetFiltering?: (filepath: string, codemod: C) => boolean;
5
8
  preCodemodRun?: (codemod: C) => Promise<void>;
@@ -18,4 +21,6 @@ export declare function runCodemod<C extends Codemod>(codemod: C, transformation
18
21
  hasChanges: boolean;
19
22
  content: string;
20
23
  }, Error>>>;
24
+ export declare function findAndReplace(content: SgRoot<TypesMap>, rule: Rule<TypesMap>, transformer: (node: SgNode<TypesMap, Kinds<TypesMap>>) => Optional<string>): string;
25
+ export declare function commitEditModifications(edits: Array<Edit>, modifications: Modifications): Promise<Modifications>;
21
26
  export {};
package/dist/index.cjs CHANGED
@@ -33,7 +33,9 @@ var __webpack_require__ = {};
33
33
  var __webpack_exports__ = {};
34
34
  __webpack_require__.r(__webpack_exports__);
35
35
  __webpack_require__.d(__webpack_exports__, {
36
+ commitEditModifications: ()=>commitEditModifications,
36
37
  runCodemod: ()=>runCodemod,
38
+ findAndReplace: ()=>findAndReplace,
37
39
  runCodemods: ()=>runCodemods
38
40
  });
39
41
  const external_node_path_namespaceObject = require("node:path");
@@ -44,6 +46,7 @@ const external_fast_glob_namespaceObject = require("fast-glob");
44
46
  var external_fast_glob_default = /*#__PURE__*/ __webpack_require__.n(external_fast_glob_namespaceObject);
45
47
  const external_neverthrow_namespaceObject = require("neverthrow");
46
48
  const napi_namespaceObject = require("@ast-grep/napi");
49
+ const kamaal_namespaceObject = require("@kamaalio/kamaal");
47
50
  const JAVASCRIPT_EXTENSIONS = [
48
51
  '.js',
49
52
  '.cjs',
@@ -125,6 +128,33 @@ async function runCodemod(codemod, transformationPath, options) {
125
128
  }
126
129
  }));
127
130
  }
131
+ function findAndReplace(content, rule, transformer) {
132
+ const root = content.root();
133
+ const edits = kamaal_namespaceObject.arrays.compactMap(root.findAll({
134
+ rule
135
+ }), (node)=>{
136
+ const transformed = transformer(node);
137
+ if (null == transformed) return null;
138
+ return node.replace(transformed);
139
+ });
140
+ return root.commitEdits(edits);
141
+ }
142
+ async function commitEditModifications(edits, modifications) {
143
+ if (0 === edits.length) return modifications;
144
+ const root = modifications.ast.root();
145
+ const committed = root.commitEdits(edits);
146
+ const modifiedAST = await (0, napi_namespaceObject.parseAsync)(modifications.lang, committed);
147
+ return {
148
+ ...modifications,
149
+ ast: modifiedAST,
150
+ report: {
151
+ changesApplied: modifications.report.changesApplied + edits.length
152
+ },
153
+ history: modifications.history.concat([
154
+ modifiedAST
155
+ ])
156
+ };
157
+ }
128
158
  function defaultedOptions(options) {
129
159
  return {
130
160
  hooks: defaultedHooks(options?.hooks),
@@ -142,9 +172,13 @@ function defaultedHooks(hooks) {
142
172
  preCodemodRun
143
173
  };
144
174
  }
175
+ exports.commitEditModifications = __webpack_exports__.commitEditModifications;
176
+ exports.findAndReplace = __webpack_exports__.findAndReplace;
145
177
  exports.runCodemod = __webpack_exports__.runCodemod;
146
178
  exports.runCodemods = __webpack_exports__.runCodemods;
147
179
  for(var __webpack_i__ in __webpack_exports__)if (-1 === [
180
+ "commitEditModifications",
181
+ "findAndReplace",
148
182
  "runCodemod",
149
183
  "runCodemods"
150
184
  ].indexOf(__webpack_i__)) exports[__webpack_i__] = __webpack_exports__[__webpack_i__];
package/dist/index.d.ts CHANGED
@@ -1 +1 @@
1
- export { runCodemods, runCodemod, type Codemod, type Modifications } from './codemods/index.js';
1
+ export { runCodemods, runCodemod, commitEditModifications, findAndReplace, type Codemod, type Modifications, } from './codemods/index.js';
package/dist/index.js CHANGED
@@ -2,7 +2,8 @@ import node_path from "node:path";
2
2
  import promises from "node:fs/promises";
3
3
  import fast_glob from "fast-glob";
4
4
  import { err, ok } from "neverthrow";
5
- import { Lang } from "@ast-grep/napi";
5
+ import { Lang, parseAsync } from "@ast-grep/napi";
6
+ import { arrays } from "@kamaalio/kamaal";
6
7
  const JAVASCRIPT_EXTENSIONS = [
7
8
  '.js',
8
9
  '.cjs',
@@ -84,6 +85,33 @@ async function runCodemod(codemod, transformationPath, options) {
84
85
  }
85
86
  }));
86
87
  }
88
+ function findAndReplace(content, rule, transformer) {
89
+ const root = content.root();
90
+ const edits = arrays.compactMap(root.findAll({
91
+ rule
92
+ }), (node)=>{
93
+ const transformed = transformer(node);
94
+ if (null == transformed) return null;
95
+ return node.replace(transformed);
96
+ });
97
+ return root.commitEdits(edits);
98
+ }
99
+ async function commitEditModifications(edits, modifications) {
100
+ if (0 === edits.length) return modifications;
101
+ const root = modifications.ast.root();
102
+ const committed = root.commitEdits(edits);
103
+ const modifiedAST = await parseAsync(modifications.lang, committed);
104
+ return {
105
+ ...modifications,
106
+ ast: modifiedAST,
107
+ report: {
108
+ changesApplied: modifications.report.changesApplied + edits.length
109
+ },
110
+ history: modifications.history.concat([
111
+ modifiedAST
112
+ ])
113
+ };
114
+ }
87
115
  function defaultedOptions(options) {
88
116
  return {
89
117
  hooks: defaultedHooks(options?.hooks),
@@ -101,4 +129,4 @@ function defaultedHooks(hooks) {
101
129
  preCodemodRun
102
130
  };
103
131
  }
104
- export { runCodemod, runCodemods };
132
+ export { commitEditModifications, findAndReplace, runCodemod, runCodemods };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kamaalio/codemod-kit",
3
- "version": "0.0.13",
3
+ "version": "0.0.15",
4
4
  "type": "module",
5
5
  "author": "Kamaal Farah",
6
6
  "license": "MIT",
@@ -20,6 +20,7 @@
20
20
  ],
21
21
  "dependencies": {
22
22
  "@ast-grep/napi": "^0.38.5",
23
+ "@kamaalio/kamaal": "^0.7.6",
23
24
  "fast-glob": "^3.3.3",
24
25
  "neverthrow": "^8.2.0"
25
26
  },