Skip to content

Instantly share code, notes, and snippets.

@nmanumr
Created May 14, 2024 16:43
Show Gist options
  • Save nmanumr/7ed477d5313728807a5a91731ceaa8fc to your computer and use it in GitHub Desktop.
Save nmanumr/7ed477d5313728807a5a91731ceaa8fc to your computer and use it in GitHub Desktop.
import Bun from 'bun';
import * as ts from 'typescript';
import { factory } from 'typescript';
const glob = new Bun.Glob('src/**/*.{ts,js,jsx,tsx}');
const tImportFactory = factory.createImportDeclaration(
undefined,
factory.createImportClause(
false,
undefined,
factory.createNamedImports([factory.createImportSpecifier(
false,
undefined,
factory.createIdentifier('useTranslations')
)])
),
factory.createStringLiteral('next-intl'),
undefined
);
const tFactory = factory.createVariableStatement(
undefined,
factory.createVariableDeclarationList(
[factory.createVariableDeclaration(
factory.createIdentifier('t'),
undefined,
undefined,
factory.createCallExpression(
factory.createIdentifier('useTranslations'),
undefined,
[]
)
)],
ts.NodeFlags.Let
)
);
function extract(file: string) {
// source file to parse its AST.
let program = ts.createProgram([file], {
// 'target': 'es5',
'lib': ['dom', 'dom.iterable', 'esnext'],
'allowJs': true,
'skipLibCheck': true,
'strict': false,
'forceConsistentCasingInFileNames': true,
'noEmit': true,
'esModuleInterop': true,
// 'module': 'esnext',
// 'moduleResolution': 'node',
'resolveJsonModule': true,
'isolatedModules': true,
// 'jsx': 'preserve',
'baseUrl': '.',
'incremental': true,
'plugins': [
{
'name': 'next'
}
],
'strictNullChecks': true
});
let checker = program.getTypeChecker();
const sourceFile = program.getSourceFile(file);
const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });
let foundTranslate = false;
function nodeTransformer(context: ts.TransformationContext) {
return (sourceFile: ts.SourceFile) => {
function traverse(node: ts.Node, ctx: {isRoot: boolean, inComponent: boolean}) {
if (ctx.inComponent && ts.isCallExpression(node) && ts.isIdentifier(node.expression) && node.expression.text === 'Translate') {
foundTranslate = true;
return factory.createCallExpression(
factory.createIdentifier('t'),
node.typeArguments,
node.arguments
);
}
if (!ctx.isRoot) {
return ts.visitEachChild(node, (n) => traverse(n, ctx), context);
}
let newCtx = {
isRoot: ctx.isRoot as boolean,
inComponent: ctx.inComponent,
}
let newNode = node;
let returnType;
if (ts.isFunctionDeclaration(node) || ts.isArrowFunction(node) || ts.isFunctionExpression(node)) {
newCtx.isRoot = false;
returnType = checker.typeToString(checker.getTypeAtLocation(node));
returnType = returnType?.split('=>')?.at(-1)?.trim();
}
if (returnType !== 'Element') {
return ts.visitEachChild(newNode, (n) => traverse(n, newCtx), context);
} else {
newCtx.inComponent = true;
}
if (ts.isFunctionDeclaration(node) && node.body) {
newNode = ts.factory.createFunctionDeclaration(
node.modifiers,
node.asteriskToken,
node.name,
node.typeParameters,
node.parameters,
node.type,
factory.createBlock([
tFactory,
...node.body.statements!
])
);
}
if (ts.isArrowFunction(node) && node.body && ts.isBlock(node.body)) {
newNode = factory.createArrowFunction(
node.modifiers,
node.typeParameters,
node.parameters,
node.type,
node.equalsGreaterThanToken,
factory.createBlock([
tFactory,
...node.body.statements!
])
);
}
if (ts.isFunctionExpression(node) && node.body && ts.isBlock(node.body)) {
newNode = factory.createFunctionExpression(
node.modifiers,
node.asteriskToken,
node.name,
node.typeParameters,
node.parameters,
node.type,
factory.createBlock([
tFactory,
...node.body.statements!
])
);
}
return ts.visitEachChild(newNode, (n) => traverse(n, newCtx), context);
}
const newSource = ts.visitNode(sourceFile, (n) => traverse(n, {isRoot: true, inComponent: false}));
if (ts.isSourceFile(newSource)) {
return ts.factory.createSourceFile([
tImportFactory,
...newSource.statements
], newSource.endOfFileToken as any, newSource.flags);
}
return newSource;
};
}
if (!sourceFile) {
console.log('No source file found.');
return [undefined, foundTranslate];
}
const transformed = ts.transform(sourceFile, [nodeTransformer]);
const updatedSourceFile = factory.updateSourceFile(sourceFile, transformed.transformed as any);
return [printer.printFile(foundTranslate ? updatedSourceFile : sourceFile), foundTranslate] as const;
}
for await (const file of glob.scan('.')) {
// for (const file of ['src/helper/BasketHelper/BasketHelper.js']) {
const [newProgram, foundTranslate] = extract(file);
if (newProgram && foundTranslate) {
console.log('Updating ', file);
await Bun.write(file, newProgram);
} else {
console.log('Skipping ', file);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment