Skip to content

Instantly share code, notes, and snippets.

@hi-ogawa
Created May 29, 2022 13:26
Show Gist options
  • Save hi-ogawa/cb338b4765d25321b120b2a47819abcc to your computer and use it in GitHub Desktop.
Save hi-ogawa/cb338b4765d25321b120b2a47819abcc to your computer and use it in GitHub Desktop.
typescript-transformer-api
/* eslint-disable no-console */
import * as fs from "fs";
import * as process from "process";
import * as ts from "typescript";
//
// analyze statements of the following forms:
//
// const someVariable = someFunction(someObjectLiteral)
//
//
//
// transformer
//
const FUNCTION_NAME = "someFunction";
interface Call {
name: string;
data: Record<string, Record<string, string>>;
}
function analyzeCall(node: ts.ObjectLiteralExpression): Call["data"] {
const result: Record<string, Record<string, string>> = {};
for (const prop1 of node.properties) {
if (
prop1.name &&
(ts.isIdentifier(prop1.name) || ts.isStringLiteral(prop1.name)) &&
ts.isPropertyAssignment(prop1) &&
ts.isObjectLiteralExpression(prop1.initializer)
) {
result[prop1.name.text] = {};
for (const prop2 of prop1.initializer.properties) {
if (prop2.name && ts.isPropertyAssignment(prop2)) {
// this includes computed property names
result[prop1.name.text][prop2.name.getText()] = prop2.initializer.getText();
} else {
// object spreads come here
console.error("ERROR: unexpected prop2", prop1.getText());
}
}
} else {
console.error("ERROR: unexpected prop1", prop1.getText());
}
}
return result;
}
class Extractor {
public calls: Call[] = [];
createTransformer(): ts.TransformerFactory<ts.SourceFile> {
const extractor = this;
function createVisitor(ctx: ts.TransformationContext): ts.Visitor {
return function visitor(node: ts.Node): ts.Node {
if (ts.isVariableDeclaration(node)) {
if (node.initializer && ts.isCallExpression(node.initializer)) {
const { expression, arguments: args } = node.initializer;
if (ts.isIdentifier(expression) && expression.text === FUNCTION_NAME) {
if (
ts.isIdentifier(node.name) &&
args.length === 1 &&
ts.isObjectLiteralExpression(args[0])
) {
extractor.calls.push({
name: node.name.text,
data: analyzeCall(args[0])
});
} else {
console.error("ERROR: unexpected call", node);
}
}
}
}
return ts.visitEachChild(node, visitor, ctx);
};
}
return (ctx: ts.TransformationContext) => {
return (sourceFile: ts.SourceFile) => {
return ts.visitNode(sourceFile, createVisitor(ctx));
};
};
}
}
//
// driver (cf. https://github.com/formatjs/formatjs/blob/3e1aa52c32f93bc50efd46827f3c99e2bfac6d7b/packages/ts-transformer/tests/index.test.ts#L96)
//
async function extractCalls(filename: string): Promise<Call[]> {
const content = await fs.promises.readFile(filename, "utf-8");
const extractor = new Extractor();
ts.transpileModule(content, {
compilerOptions: {
target: ts.ScriptTarget.ESNext,
allowJs: true
},
fileName: filename,
reportDiagnostics: true,
transformers: {
before: [extractor.createTransformer()]
}
});
return extractor.calls;
}
async function main() {
const filenames = process.argv.slice(2);
const result: Record<string, Call[]> = {};
for (const filename of filenames) {
console.error(`:: processing "${filename}" ...`);
result[filename] = await extractCalls(filename);
}
console.log(JSON.stringify(result, null, 2));
}
if (require.main === module) {
main();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment