Skip to content

Commit d0fda25

Browse files
committed
added ast-grep code patcher
1 parent b6f2a11 commit d0fda25

File tree

4 files changed

+216
-2
lines changed

4 files changed

+216
-2
lines changed

packages/open-next/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
"README.md"
3838
],
3939
"dependencies": {
40+
"@ast-grep/napi": "^0.35.0",
4041
"@aws-sdk/client-cloudfront": "3.398.0",
4142
"@aws-sdk/client-dynamodb": "^3.398.0",
4243
"@aws-sdk/client-lambda": "^3.398.0",
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
// Mostly copied from the cloudflare adapter
2+
import { readFileSync } from "node:fs";
3+
4+
import {
5+
type Edit,
6+
Lang,
7+
type NapiConfig,
8+
type SgNode,
9+
parse,
10+
} from "@ast-grep/napi";
11+
import yaml from "yaml";
12+
import type { PatchCodeFn } from "./codePatcher";
13+
14+
/**
15+
* fix has the same meaning as in yaml rules
16+
* see https://ast-grep.github.io/guide/rewrite-code.html#using-fix-in-yaml-rule
17+
*/
18+
export type RuleConfig = NapiConfig & { fix?: string };
19+
20+
/**
21+
* Returns the `Edit`s and `Match`es for an ast-grep rule in yaml format
22+
*
23+
* The rule must have a `fix` to rewrite the matched node.
24+
*
25+
* Tip: use https://ast-grep.github.io/playground.html to create rules.
26+
*
27+
* @param rule The rule. Either a yaml string or an instance of `RuleConfig`
28+
* @param root The root node
29+
* @param once only apply once
30+
* @returns A list of edits and a list of matches.
31+
*/
32+
export function applyRule(
33+
rule: string | RuleConfig,
34+
root: SgNode,
35+
{ once = false } = {},
36+
) {
37+
const ruleConfig: RuleConfig =
38+
typeof rule === "string" ? yaml.parse(rule) : rule;
39+
if (ruleConfig.transform) {
40+
throw new Error("transform is not supported");
41+
}
42+
if (!ruleConfig.fix) {
43+
throw new Error("no fix to apply");
44+
}
45+
46+
const fix = ruleConfig.fix;
47+
48+
const matches = once
49+
? [root.find(ruleConfig)].filter((m) => m !== null)
50+
: root.findAll(ruleConfig);
51+
52+
const edits: Edit[] = [];
53+
54+
matches.forEach((match) => {
55+
edits.push(
56+
match.replace(
57+
// Replace known placeholders by their value
58+
fix
59+
.replace(/\$\$\$([A-Z0-9_]+)/g, (_m, name) =>
60+
match
61+
.getMultipleMatches(name)
62+
.map((n) => n.text())
63+
.join(""),
64+
)
65+
.replace(
66+
/\$([A-Z0-9_]+)/g,
67+
(m, name) => match.getMatch(name)?.text() ?? m,
68+
),
69+
),
70+
);
71+
});
72+
73+
return { edits, matches };
74+
}
75+
76+
/**
77+
* Parse a file and obtain its root.
78+
*
79+
* @param path The file path
80+
* @param lang The language to parse. Defaults to TypeScript.
81+
* @returns The root for the file.
82+
*/
83+
export function parseFile(path: string, lang = Lang.TypeScript) {
84+
return parse(lang, readFileSync(path, { encoding: "utf-8" })).root();
85+
}
86+
87+
/**
88+
* Patches the code from by applying the rule.
89+
*
90+
* This function is mainly for on off edits and tests,
91+
* use `getRuleEdits` to apply multiple rules.
92+
*
93+
* @param code The source code
94+
* @param rule The astgrep rule (yaml or NapiConfig)
95+
* @param lang The language used by the source code
96+
* @param lang Whether to apply the rule only once
97+
* @returns The patched code
98+
*/
99+
export function patchCode(
100+
code: string,
101+
rule: string | RuleConfig,
102+
{ lang = Lang.TypeScript, once = false } = {},
103+
): string {
104+
const node = parse(lang, code).root();
105+
const { edits } = applyRule(rule, node, { once });
106+
return node.commitEdits(edits);
107+
}
108+
109+
export function createPatchCode(
110+
rule: string | RuleConfig,
111+
lang = Lang.TypeScript,
112+
): PatchCodeFn {
113+
return async ({ code }) => patchCode(code, rule, { lang });
114+
}

packages/open-next/src/build/patch/codePatcher.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,7 @@ export interface VersionedField<T> {
1111
field: T;
1212
}
1313

14-
//TODO: create a version of this that would use ast-grep
15-
type PatchCodeFn = (args: {
14+
export type PatchCodeFn = (args: {
1615
// The content of the file that needs to be patched
1716
code: string;
1817
// The final path of the file that needs to be patched

pnpm-lock.yaml

Lines changed: 100 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)