-
Notifications
You must be signed in to change notification settings - Fork 4
TypeScriptASTTransformer
The TypeScriptASTTransformer
utility is designed to be able to manipulate a TypeScript
AST structure and apply different changes to it. It is platform agnostic and is only concerned with the modification of a ts.SourceFile
. As such, it expects a source file as input and exposes multiple utility methods that stage changes for the source file. The changes are later applied consecutively during finalization.
Currently it can:
- add new members to object literals
- modify existing members of object literals
- create new object literal expressions
- prepend/append members to array literals
- it supports an optional anchor element that it can prepend/append elements around
- create new array literal expressions
- create new import declarations
- add identifiers to existing import declarations
- detect collisions between existing import declarations
- locate variable declarations by given name and type
- look up a node's ancestor and check it against a condition
- look up a
ts.PropertyAssignment
in an object literal - look up an identifier/element in an array literal
- create a call expression of the form
x.call<T>(args)
- where the type argument and the method arguments are optional
- transform the AST into source code and apply formatting
In the transformer, there are methods that can be used to create new nodes. They are wrappers around methods of the same/similar names in the ts.factory
and are exposed for ease of use. They include:
-
createObjectLiteralExpression
- creates ats.ObjectLiteralExpression
with a set of key-value pair properties- it has an optional
transform
delegate that can be used to mutate the object's properties' values to ats.LiteralExpression
, by default it will transform them to ats.StringLiteral
- the newly-created object literal can be on single or multiple lines
- it has an optional
-
createArrayLiteralExpression
- creates ats.ArrayLiteralExpression
with the provided elements- it supports both primitive and complex elements
- the newly-created array literal can be on single or multiple lines
-
createCallExpression
- creates ats.CallExpression
for a given identifier that calls a method
For example:
const typeArg = ts.factory.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword);
const arg = ts.factory.createNumericLiteral("5");
const callExpression = astTransformer.createCallExpression("x", "myGenericFunction", [typeArg], [arg]);
The callExpression
variable will contain a ts.CallExpression
node which if printed will look something like this:
x.myGenericFunction<number>(5);
-
createImportDeclaration
- creates a node for ats.ImportDeclaration
that can be:- side effects imports -
import "my-module;"
- default imports -
import X from "my-module";
- imports with named bindings -
import { X, Y... } from "my-module";
- side effects imports -
Additionally, there are methods that can be used to request changes in the AST. All of them require a conditional predicate that is used when drilling down to the appropriate node. These methods include:
-
requestNewMemberInObjectLiteral
- creates a request for an update in the AST that will add a new member (ts.PropertyAssignment
) in an object literal expression -
requestJsxMemberInObjectLiteral
- similar torequestNewMemberInObjectLiteral
with the only difference being that the value of the created member will be ats.JsxSelfClosingElement
-
requestUpdateForObjectLiteralMember
- creates a request for an update in the AST that will change the value of a member in an object literal -
requestNewMembersInArrayLiteral
- creates a request for an update in the AST that will addn
members in a particularts.ArrayLiteralExpression
- supports an optional
anchorElement
that can be used to prepend/append the new nods around
- supports an optional
-
requestNewImportDeclaration
- creates a request for an update in the AST that will add a new import declaration of the forms outlined increateImportDeclaration
An example usage of createObjectLiteralExpression
alongside requestNewMembersInArrayLiteral
can be:
const newObjectLiteral = this.astTransformer.createObjectLiteralExpression([
{ name: "path", value: ts.factory.createStringLiteral("some-new-path") },
{ name: "component", value: ts.factory.createIdentifier("MyComponent") },
]);
// the condition that will be used when traversing the AST
const condition = (node: ts.ArrayLiteralExpression) =>
node.elements.some((e) => ts.isObjectLiteralExpression(e)
&& e.properties.some((p) => ts.isPropertyAssignment(p) && p.name.getText() === "path")
);
this.astTransformer.requestNewMembersInArrayLiteral(condition, [newObjectLiteral]);
This will create a new object ({ path: "some-new-path", component: MyComponent }
) and add it to an array literal that has an object literal member with property with a value path
.
So this:
const routes: Route[] = [{ path: "some-path", component: SomeComponent }];
Will become this:
const routes: Route[] = [
{ path: "some-path", component: SomeComponent },
{ path: "some-new-path", component: MyComponent }
];
Keep in mind that this is a very crude and simplified example as in reality additional checks will have to be done to make sure that the node that is being added goes precisely where it is supposed to.
Additionally, regarding any of the exposed utilities, the transformer will not attempt to fix potentially broken code as it is only concerned with the modification of the AST and not whether or not the resulting code is actually runnable.
The TypeScriptASTTransformer
will only store the requested changes and will not apply any of them until either finalize
or applyChanges
is called.
-
applyChanges
- applies the aggregated changes to thets.SourceFile
and returns the resulting AST, does not modify the original one -
finalize
- callsapplyChanges
internally and then prints the resulting source code, a formatter can be used to make the code prettier
Note
Calling either of these will clear the transformer's cache of requested changes.
The TypeScriptFormattingService
is a utility used by the transformer after applying the changes to the AST and finalizing the source. This service will read a project's .editorconfig
and attempt to format the parsed AST by using the ts.LanguageService
's formatting capabilities. It is a completely standalone utility that can be replaced with a custom one if needed.