blob: 53a7f0d9c63a66526bdd1be525241b2eefdcf9af [file] [log] [blame]
// Copyright (C) 2019 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import * as ts from 'typescript';
import {SyntaxKind} from 'typescript';
import {Node} from 'typescript';
export function assertNodeKind<T extends U, U extends ts.Node>(node: U, expectedKind: ts.SyntaxKind): T {
if (node.kind !== expectedKind) {
throw new Error(`Invlid node kind. Expected: ${ts.SyntaxKind[expectedKind]}, actual: ${ts.SyntaxKind[node.kind]}`);
}
return node as T;
}
export function assertNodeKindOrUndefined<T extends U, U extends ts.Node>(node: U | undefined, expectedKind: ts.SyntaxKind): T | undefined {
if (!node) {
return undefined;
}
return assertNodeKind<T, U>(node, expectedKind);
}
export function getPropertyAssignment(expression?: ts.ObjectLiteralElementLike): ts.PropertyAssignment | undefined {
return assertNodeKindOrUndefined(expression, ts.SyntaxKind.PropertyAssignment);
}
export function getStringLiteralValue(expression: ts.Expression): string {
const literal: ts.StringLiteral = assertNodeKind(expression, ts.SyntaxKind.StringLiteral);
return literal.text;
}
export function getBooleanLiteralValue(expression: ts.Expression): boolean {
if (expression.kind === ts.SyntaxKind.TrueKeyword) {
return true;
}
if (expression.kind === ts.SyntaxKind.FalseKeyword) {
return false;
}
throw new Error(`Invalid expression kind - ${expression.kind}`);
}
export function getObjectLiteralExpression(expression: ts.Expression): ts.ObjectLiteralExpression {
return assertNodeKind(expression, ts.SyntaxKind.ObjectLiteralExpression);
}
export function getArrayLiteralExpression(expression: ts.Expression): ts.ArrayLiteralExpression {
return assertNodeKind(expression, ts.SyntaxKind.ArrayLiteralExpression);
}
export function replaceNode(file: ts.SourceFile, originalNode: ts.Node, newNode: ts.Node): ts.TransformationResult<ts.SourceFile> {
const nodeReplacerTransformer: ts.TransformerFactory<ts.SourceFile> = (context: ts.TransformationContext) => {
const visitor: ts.Visitor = (node) => {
if(node === originalNode) {
return newNode;
}
return ts.visitEachChild(node, visitor, context);
};
return source => ts.visitNode(source, visitor);
};
return ts.transform(file, [nodeReplacerTransformer]);
}
export type NameExpression = ts.Identifier | ts.ThisExpression | ts.PropertyAccessExpression;
export function createNameExpression(fullPath: string): NameExpression {
const parts = fullPath.split(".");
let result: NameExpression = parts[0] === "this" ? ts.createThis() : ts.createIdentifier(parts[0]);
for(let i = 1; i < parts.length; i++) {
result = ts.createPropertyAccess(result, parts[i]);
}
return result;
}
const generatedCommentNewLineAfterText = "-Generated code - new line after - 9cb292bc-5d88-4c5e-88f4-49535c93beb9 -";
const generatedCommentNewLineBeforeText = "-Generated code - new line-before - 9cb292bc-5d88-4c5e-88f4-49535c93beb9 -";
const generatedCommentNewLineAfterRegExp = new RegExp("//" + generatedCommentNewLineAfterText, 'g');
const generatedCommentNewLineBeforeRegExp = new RegExp("//" + generatedCommentNewLineBeforeText + "\n", 'g');
const replacableCommentText = "- Replacepoint - 9cb292bc-5d88-4c5e-88f4-49535c93beb9 -";
export function addNewLineAfterNode<T extends ts.Node>(node: T): T {
const comment = ts.getSyntheticTrailingComments(node);
if(comment && comment.some(c => c.text === generatedCommentNewLineAfterText)) {
return node;
}
return ts.addSyntheticTrailingComment(node, ts.SyntaxKind.SingleLineCommentTrivia, generatedCommentNewLineAfterText, true);
}
export function addNewLineBeforeNode<T extends ts.Node>(node: T): T {
const comment = ts.getSyntheticLeadingComments(node);
if(comment && comment.some(c => c.text === generatedCommentNewLineBeforeText)) {
return node;
}
return ts.addSyntheticLeadingComment(node, ts.SyntaxKind.SingleLineCommentTrivia, generatedCommentNewLineBeforeText, true);
}
export function applyNewLines(text: string): string {
return text.replace(generatedCommentNewLineAfterRegExp, "").replace(generatedCommentNewLineBeforeRegExp, "");
}
export function addReplacableCommentAfterNode<T extends ts.Node>(node: T, name: string): T {
return ts.addSyntheticTrailingComment(node, ts.SyntaxKind.SingleLineCommentTrivia, replacableCommentText + name, true);
}
export function addReplacableCommentBeforeNode<T extends ts.Node>(node: T, name: string): T {
return ts.addSyntheticLeadingComment(node, ts.SyntaxKind.SingleLineCommentTrivia, replacableCommentText + name, true);
}
export function replaceComment(text: string, commentName: string, newContent: string): string {
return text.replace("//" + replacableCommentText + commentName, newContent);
}
export function createMethod(name: string, methodDecl: ts.MethodDeclaration | undefined, codeAtStart: ts.Statement[], codeAtEnd: ts.Statement[], callSuperMethod: boolean): ts.MethodDeclaration | undefined {
if(!methodDecl && (codeAtEnd.length > 0 || codeAtEnd.length > 0)) {
methodDecl = ts.createMethod([], [], undefined, name, undefined, [], [],undefined, ts.createBlock([]));
}
if(!methodDecl) {
return;
}
if (!methodDecl.body) {
throw new Error("Method must have a body");
}
if(methodDecl.parameters.length > 0) {
throw new Error("Methods with parameters are not supported");
}
let newStatements = [...codeAtStart];
if(callSuperMethod) {
const superCall: ts.CallExpression = ts.createCall(ts.createPropertyAccess(ts.createSuper(), assertNodeKind(methodDecl.name, ts.SyntaxKind.Identifier) as ts.Identifier), [], []);
const superCallExpression = ts.createExpressionStatement(superCall);
newStatements.push(superCallExpression);
}
newStatements.push(...codeAtEnd);
const newBody = ts.getMutableClone(methodDecl.body);
newStatements = newStatements.map(m => addNewLineAfterNode(m));
newStatements.splice(codeAtStart.length + 1, 0, ...newBody.statements);
newBody.statements = ts.createNodeArray(newStatements);
const newMethod = ts.getMutableClone(methodDecl);
newMethod.body = newBody;
return newMethod;
}
export function restoreLeadingComments<T extends Node>(node: T, originalComments: string[]): T {
if(originalComments.length === 0) {
return node;
}
for(const comment of originalComments) {
if(comment.startsWith("//")) {
node = ts.addSyntheticLeadingComment(node, SyntaxKind.SingleLineCommentTrivia, comment.substr(2), true);
} else if(comment.startsWith("/*")) {
if(!comment.endsWith("*/")) {
throw new Error(`Not support comment: ${comment}`);
}
node = ts.addSyntheticLeadingComment(node, SyntaxKind.MultiLineCommentTrivia, comment.substr(2, comment.length - 4), true);
} else {
throw new Error(`Not supported comment: ${comment}`);
}
}
return node;
}
export function getLeadingComments(node: ts.Node): string[] {
const nodeText = node.getFullText();
const commentRanges = ts.getLeadingCommentRanges(nodeText, 0);
if(!commentRanges) {
return [];
}
return commentRanges.map(range => nodeText.substring(range.pos, range.end))
}