chore: import upstream snapshot with attribution
Deploy (to testing) and Test Playground Preview Worker / Deploy Playground Preview Worker (testing) (push) Has been skipped
Deploy Workers Shared Staging / Deploy Workers Shared Staging (push) Failing after 0s
Prerelease / build (push) Has been skipped
Handle Changesets / Handle Changesets (push) Has been cancelled
Semgrep OSS scan / semgrep-oss (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 12:30:11 +08:00
commit 70bf21e064
5213 changed files with 731895 additions and 0 deletions
+75
View File
@@ -0,0 +1,75 @@
import { mkdirSync, writeFileSync } from "node:fs";
import { dirname, join, relative, resolve } from "node:path";
import * as recast from "recast";
import { parseFile, parseTs } from "./index";
/**
* Writing code-mods often requires some trial and error.
* Often manual testing can become a hassle. This script was meant
* to help test and develop transforms in isolation without having to write a throw-away script.
*
* Replace your codemod below and run the script with `pnpm run dev`
*/
/**
* This function mocks the `transformFile` API but outputs it to the console and writes it
* to a dedicated output file for easier testing.
*/
export const testTransform = (
filePath: string,
methods: recast.types.Visitor
) => {
const devSnippetsDir = resolve(__dirname, "../dev-snippets");
const resolvedInput = resolve(__dirname, filePath);
const relativeInput = relative(devSnippetsDir, resolvedInput);
if (
relativeInput.startsWith("..") ||
resolve(relativeInput) === relativeInput
) {
throw new Error(`Input file must be under dev-snippets/. Got: ${filePath}`);
}
const ast = parseFile(resolvedInput);
if (ast) {
recast.visit(ast, methods);
const code = recast.print(ast).code;
console.log(code);
const outputPath = join(
__dirname,
"../dev-snippets-outputs",
relativeInput
);
mkdirSync(dirname(outputPath), {
recursive: true,
});
writeFileSync(outputPath, code);
}
};
// Use this function to experiment with a codemod in isolation
const testCodemod = () => {
testTransform("../dev-snippets/test.ts", {
visitIdentifier(n) {
n.node.name = "Potato";
return false;
},
});
};
testCodemod();
// This function can be used to inspect the AST of a particular snippet
const _printSnippet = () => {
const snippet = `
if(true) {
console.log("potato");
}
`;
const program = parseTs(snippet).program;
console.log(program.body[0]);
};
// _printSnippet();
+124
View File
@@ -0,0 +1,124 @@
import { readFileSync, writeFileSync } from "node:fs";
import path from "node:path";
import * as recast from "recast";
import * as esprimaParser from "recast/parsers/esprima";
import * as typescriptParser from "recast/parsers/typescript";
import type { Program } from "esprima";
/*
CODEMOD TIPS & TRICKS
=====================
More info about parsing and transforming can be found in the `recast` docs:
https://github.com/benjamn/recast
`recast` uses the `ast-types` library under the hood for basic AST operations
and defining node types. If you need to manipulate or manually construct AST nodes as
part of a code mod operation, be sure to check the `ast-types` documentation:
https://github.com/benjamn/ast-types
Last but not least, AST viewers can be extremely helpful when trying to write
a transformer:
- https://astexplorer.net/
- https://ts-ast-viewer.com/#
*/
// Parse an input string as javascript and return an ast
export function parseJs(src: string) {
src = src.trim();
try {
return recast.parse(src, { parser: esprimaParser });
} catch {
throw new Error("Error parsing js template.");
}
}
// Parse an input string as typescript and return an ast
export function parseTs(src: string) {
src = src.trim();
try {
return recast.parse(src, { parser: typescriptParser });
} catch {
throw new Error("Error parsing ts template.");
}
}
// Parse a provided file with recast and return an ast
// Selects the correct parser based on the file extension
export function parseFile(filePath: string) {
const lang = path.extname(filePath).slice(1);
const parser = lang === "js" ? esprimaParser : typescriptParser;
try {
const fileContents = readFileSync(path.resolve(filePath), "utf-8");
if (fileContents) {
return recast.parse(fileContents, { parser }).program as Program;
}
} catch {
throw new Error(`Error parsing file: ${filePath}`);
}
return null;
}
// Transform a file with the provided transformer methods and write it back to disk
export function transformFile(filePath: string, methods: recast.types.Visitor) {
const ast = parseFile(filePath);
if (ast) {
recast.visit(ast, methods);
writeFileSync(filePath, recast.print(ast).code);
}
}
/**
* merges provided properties into a given object (updating the object itself), deeply merging them in case
* some properties are object themselves
*
* @param sourceObject the object into which merge the new properties
* @param newProperties the new properties to add/merge
*/
export function mergeObjectProperties(
sourceObject: recast.types.namedTypes.ObjectExpression,
newProperties: recast.types.namedTypes.ObjectProperty[]
): void {
newProperties.forEach((newProp) => {
const newPropName = getPropertyName(newProp);
if (!newPropName) {
return false;
}
const indexOfExisting = sourceObject.properties.findIndex(
(p) => p.type === "ObjectProperty" && getPropertyName(p) === newPropName
);
const existing = sourceObject.properties[indexOfExisting];
if (!existing) {
sourceObject.properties.push(newProp);
return;
}
if (
existing.type === "ObjectProperty" &&
existing.value.type === "ObjectExpression" &&
newProp.value.type === "ObjectExpression"
) {
mergeObjectProperties(
existing.value,
newProp.value.properties as recast.types.namedTypes.ObjectProperty[]
);
return;
}
sourceObject.properties[indexOfExisting] = newProp;
});
}
function getPropertyName(newProp: recast.types.namedTypes.ObjectProperty) {
return newProp.key.type === "Identifier"
? newProp.key.name
: newProp.key.type === "StringLiteral"
? newProp.key.value
: null;
}