Skip to content
This repository was archived by the owner on Oct 5, 2021. It is now read-only.

Commit 4d75d77

Browse files
committed
feat: Implement instrumentation as a TypeScript Compiler TransformerInitial implementation of #41
1 parent bf3239d commit 4d75d77

File tree

2 files changed

+115
-0
lines changed

2 files changed

+115
-0
lines changed
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import * as ts from 'typescript';
2+
import { transformSourceCode } from './transformer';
3+
4+
function astPrettyPrint(sourceText: string) {
5+
const printer: ts.Printer = ts.createPrinter();
6+
return printer.printFile(ts.createSourceFile('test.ts', sourceText, ts.ScriptTarget.Latest));
7+
}
8+
9+
describe('transformer', () => {
10+
it('should instrument function parameters without types', () => {
11+
const input = `function (a) { return 5; }`;
12+
expect(transformSourceCode(input, 'test.ts')).toMatch(
13+
astPrettyPrint(`function (a) { $_$twiz("a", a, 11, "test.ts", {}); return 5; }`),
14+
);
15+
});
16+
17+
it('should instrument class method parameters', () => {
18+
const input = `class Foo { bar(a) { return 5; } }`;
19+
expect(transformSourceCode(input, 'test.ts')).toMatch(
20+
astPrettyPrint(`class Foo { bar(a) { $_$twiz("a", a, 17, "test.ts", {}); return 5; } }`),
21+
);
22+
});
23+
});
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import * as ts from 'typescript';
2+
3+
function updateFunction(node: ts.FunctionDeclaration, instrumentStatements: ReadonlyArray<ts.Statement>) {
4+
return ts.updateFunctionDeclaration(
5+
node,
6+
node.decorators,
7+
node.modifiers,
8+
node.asteriskToken,
9+
node.name,
10+
node.typeParameters,
11+
node.parameters,
12+
node.type,
13+
ts.createBlock([...instrumentStatements, ...(node.body ? node.body.statements : [])]),
14+
);
15+
}
16+
17+
function updateMethod(node: ts.MethodDeclaration, instrumentStatements: ReadonlyArray<ts.Statement>) {
18+
return ts.updateMethod(
19+
node,
20+
node.decorators,
21+
node.modifiers,
22+
node.asteriskToken,
23+
node.name,
24+
node.questionToken,
25+
node.typeParameters,
26+
node.parameters,
27+
node.type,
28+
ts.createBlock([...instrumentStatements, ...(node.body ? node.body.statements : [])]),
29+
);
30+
}
31+
32+
export function visitorFactory(ctx: ts.TransformationContext, source: ts.SourceFile) {
33+
const visitor: ts.Visitor = (node: ts.Node): ts.Node => {
34+
if (ts.isFunctionDeclaration(node) || ts.isMethodDeclaration(node)) {
35+
const instrumentStatements: ts.Statement[] = [];
36+
for (const param of node.parameters) {
37+
if (!param.type && !param.initializer && node.body) {
38+
const typeInsertionPos = param.name.getEnd() + (param.questionToken ? 1 : 0);
39+
// const opts: IExtraOptions = {};
40+
// if (isArrow) {
41+
// opts.arrow = true;
42+
// }
43+
// if (!hasParensAroundArguments(node)) {
44+
// opts.parens = [node.parameters[0].getStart(), node.parameters[0].getEnd()];
45+
// }
46+
instrumentStatements.push(
47+
ts.createStatement(
48+
ts.createCall(
49+
ts.createIdentifier('$_$twiz'),
50+
[],
51+
[
52+
ts.createLiteral(param.name.getText()),
53+
ts.createIdentifier(param.name.getText()),
54+
ts.createNumericLiteral(typeInsertionPos.toString()),
55+
ts.createLiteral(source.fileName),
56+
ts.createObjectLiteral(), // TODO: opts
57+
],
58+
),
59+
),
60+
);
61+
}
62+
if (ts.isFunctionDeclaration(node)) {
63+
return ts.visitEachChild(updateFunction(node, instrumentStatements), visitor, ctx);
64+
}
65+
if (ts.isMethodDeclaration(node)) {
66+
return ts.visitEachChild(updateMethod(node, instrumentStatements), visitor, ctx);
67+
}
68+
}
69+
}
70+
71+
return ts.visitEachChild(node, visitor, ctx);
72+
};
73+
74+
return visitor;
75+
}
76+
77+
export function transformer() {
78+
return (ctx: ts.TransformationContext): ts.Transformer<ts.SourceFile> => {
79+
return (source: ts.SourceFile) => ts.visitNode(source, visitorFactory(ctx, source));
80+
};
81+
}
82+
83+
export function transformSourceFile(sourceFile: ts.SourceFile) {
84+
return ts.transform(sourceFile, [transformer()]).transformed[0];
85+
}
86+
87+
export function transformSourceCode(sourceText: string, fileName: string) {
88+
const sourceFile = ts.createSourceFile(fileName, sourceText, ts.ScriptTarget.Latest, true);
89+
const transformed = transformSourceFile(sourceFile);
90+
const printer: ts.Printer = ts.createPrinter();
91+
return printer.printFile(transformed);
92+
}

0 commit comments

Comments
 (0)