This answer is using only the compiler api as I'm not that familiar with angular or the build process, but will hopefully be of some help and you should be able to adapt it.
- Find the component decorators that match what you're looking for.
- Transform the string literals you want to change within the decorator's call expression's first argument's object literal's property that is a property assignment with the name "selector"'s initializer.
Using my tool ts-ast-viewer.com helps to see what you need to be checking for...
// Note: This code mixes together the act of analyzing and transforming.
// You may want a stricter separation, but that requires creating an entire
// architecture around this.
import * as ts from "typescript";
// create a source file ast
const sourceFile = ts.createSourceFile("/file.ts", `import { Component } from 'whereever';
@Component({ selector: 'login' })
class Test {
}
`, ts.ScriptTarget.Latest);
// transform it
const transformerFactory: ts.TransformerFactory<ts.SourceFile> = context => {
return file => visitChangingDecorators(file, context) as ts.SourceFile;
};
const transformationResult = ts.transform(sourceFile, [transformerFactory]);
const transformedSourceFile = transformationResult.transformed[0];
// see the result by printing it
console.log(ts.createPrinter().printFile(transformedSourceFile));
function visitChangingDecorators(node: ts.Node, context: ts.TransformationContext) {
// visit all the nodes, changing any component decorators
if (ts.isDecorator(node) && isComponentDecorator(node))
return handleComponentDecorator(node);
else {
return ts.visitEachChild(node,
child => visitChangingDecorators(child, context), context);
}
}
function handleComponentDecorator(node: ts.Decorator) {
const expr = node.expression;
if (!ts.isCallExpression(expr))
return node;
const args = expr.arguments;
if (args.length !== 1)
return node;
const arg = args[0];
if (!ts.isObjectLiteralExpression(arg))
return node;
// Using these update functions on the call expression
// and decorator is kind of useless. A better implementation
// would only update the string literal that needs to be updated.
const updatedCallExpr = ts.updateCall(
expr,
expr.expression,
expr.typeArguments,
[transformObjectLiteral(arg)]
);
return ts.updateDecorator(node, updatedCallExpr);
function transformObjectLiteral(objectLiteral: ts.ObjectLiteralExpression) {
return ts.updateObjectLiteral(objectLiteral, objectLiteral.properties.map(prop => {
if (!ts.isPropertyAssignment(prop))
return prop;
if (!prop.name || !ts.isIdentifier(prop.name))
return prop;
if (prop.name.escapedText !== "selector")
return prop;
if (!ts.isStringLiteral(prop.initializer))
return prop;
if (prop.initializer.text === "login") {
return ts.updatePropertyAssignment(
prop,
prop.name,
ts.createStringLiteral("not-use-this-login")
);
}
return prop;
}));
}
}
function isComponentDecorator(node: ts.Decorator) {
// You will probably want something more sophisticated
// that analyzes the import declarations or possibly uses
// the type checker in an initial pass of the source files
// before transforming. This naively just checks if the
// decorator is a call expression and if its expression
// has the text "Component". This definitely won't work
// in every scenario and might possibly get false positives.
const expr = node.expression;
if (!ts.isCallExpression(expr))
return false;
if (!ts.isIdentifier(expr.expression))
return false;
return expr.expression.escapedText === "Component";
}
Outputs:
import { Component } from "whereever";
@Component({ selector: "not-use-this-login" })
class Test {
}