mirror of
https://github.com/angular/angular
synced 2026-05-24 09:28:37 +00:00
306 lines
7.9 KiB
TypeScript
306 lines
7.9 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright Google LLC All Rights Reserved.
|
|
*
|
|
* Use of this source code is governed by an MIT-style license that can be
|
|
* found in the LICENSE file at https://angular.dev/license
|
|
*/
|
|
|
|
import {
|
|
ArrowFunction,
|
|
AST,
|
|
AstVisitor,
|
|
ASTWithSource,
|
|
Binary,
|
|
BindingPipe,
|
|
Call,
|
|
Chain,
|
|
Conditional,
|
|
ImplicitReceiver,
|
|
Interpolation,
|
|
KeyedRead,
|
|
LiteralArray,
|
|
LiteralMap,
|
|
LiteralPrimitive,
|
|
NonNullAssert,
|
|
ParenthesizedExpression,
|
|
PrefixNot,
|
|
PropertyRead,
|
|
RecursiveAstVisitor,
|
|
RegularExpressionLiteral,
|
|
SafeCall,
|
|
SafeKeyedRead,
|
|
SafePropertyRead,
|
|
SpreadElement,
|
|
TaggedTemplateLiteral,
|
|
TemplateLiteral,
|
|
TemplateLiteralElement,
|
|
ThisReceiver,
|
|
TypeofExpression,
|
|
Unary,
|
|
VoidExpression,
|
|
} from '../../../src/expression_parser/ast';
|
|
|
|
class Unparser implements AstVisitor {
|
|
private static _quoteRegExp = /"/g;
|
|
// using non-null assertion because they're both re(set) by unparse()
|
|
private _expression!: string;
|
|
|
|
unparse(ast: AST) {
|
|
this._expression = '';
|
|
this._visit(ast);
|
|
return this._expression;
|
|
}
|
|
|
|
visitPropertyRead(ast: PropertyRead, context: any) {
|
|
this._visit(ast.receiver);
|
|
this._expression +=
|
|
ast.receiver instanceof ImplicitReceiver || ast.receiver instanceof ThisReceiver
|
|
? `${ast.name}`
|
|
: `.${ast.name}`;
|
|
}
|
|
|
|
visitUnary(ast: Unary, context: any) {
|
|
this._expression += ast.operator;
|
|
this._visit(ast.expr);
|
|
}
|
|
|
|
visitBinary(ast: Binary, context: any) {
|
|
this._visit(ast.left);
|
|
this._expression += ` ${ast.operation} `;
|
|
this._visit(ast.right);
|
|
}
|
|
|
|
visitChain(ast: Chain, context: any) {
|
|
const len = ast.expressions.length;
|
|
for (let i = 0; i < len; i++) {
|
|
this._visit(ast.expressions[i]);
|
|
this._expression += i == len - 1 ? ';' : '; ';
|
|
}
|
|
}
|
|
|
|
visitConditional(ast: Conditional, context: any) {
|
|
this._visit(ast.condition);
|
|
this._expression += ' ? ';
|
|
this._visit(ast.trueExp);
|
|
this._expression += ' : ';
|
|
this._visit(ast.falseExp);
|
|
}
|
|
|
|
visitPipe(ast: BindingPipe, context: any) {
|
|
this._expression += '(';
|
|
this._visit(ast.exp);
|
|
this._expression += ` | ${ast.name}`;
|
|
ast.args.forEach((arg) => {
|
|
this._expression += ':';
|
|
this._visit(arg);
|
|
});
|
|
this._expression += ')';
|
|
}
|
|
|
|
visitCall(ast: Call, context: any) {
|
|
this._visit(ast.receiver);
|
|
this._expression += '(';
|
|
let isFirst = true;
|
|
ast.args.forEach((arg) => {
|
|
if (!isFirst) this._expression += ', ';
|
|
isFirst = false;
|
|
this._visit(arg);
|
|
});
|
|
this._expression += ')';
|
|
}
|
|
|
|
visitSafeCall(ast: SafeCall, context: any) {
|
|
this._visit(ast.receiver);
|
|
this._expression += '?.(';
|
|
let isFirst = true;
|
|
ast.args.forEach((arg) => {
|
|
if (!isFirst) this._expression += ', ';
|
|
isFirst = false;
|
|
this._visit(arg);
|
|
});
|
|
this._expression += ')';
|
|
}
|
|
|
|
visitImplicitReceiver(ast: ImplicitReceiver, context: any) {}
|
|
|
|
visitThisReceiver(ast: ThisReceiver, context: any) {}
|
|
|
|
visitInterpolation(ast: Interpolation, context: any) {
|
|
for (let i = 0; i < ast.strings.length; i++) {
|
|
this._expression += ast.strings[i];
|
|
if (i < ast.expressions.length) {
|
|
this._expression += `{{ `;
|
|
this._visit(ast.expressions[i]);
|
|
this._expression += ` }}`;
|
|
}
|
|
}
|
|
}
|
|
|
|
visitKeyedRead(ast: KeyedRead, context: any) {
|
|
this._visit(ast.receiver);
|
|
this._expression += '[';
|
|
this._visit(ast.key);
|
|
this._expression += ']';
|
|
}
|
|
|
|
visitLiteralArray(ast: LiteralArray, context: any) {
|
|
this._expression += '[';
|
|
let isFirst = true;
|
|
ast.expressions.forEach((expression) => {
|
|
if (!isFirst) this._expression += ', ';
|
|
isFirst = false;
|
|
this._visit(expression);
|
|
});
|
|
|
|
this._expression += ']';
|
|
}
|
|
|
|
visitLiteralMap(ast: LiteralMap, context: any) {
|
|
this._expression += '{';
|
|
let isFirst = true;
|
|
for (let i = 0; i < ast.keys.length; i++) {
|
|
if (!isFirst) this._expression += ', ';
|
|
isFirst = false;
|
|
const key = ast.keys[i];
|
|
|
|
if (key.kind === 'spread') {
|
|
this._expression += '...';
|
|
} else {
|
|
this._expression += key.quoted ? JSON.stringify(key.key) : key.key;
|
|
this._expression += ': ';
|
|
}
|
|
|
|
this._visit(ast.values[i]);
|
|
}
|
|
|
|
this._expression += '}';
|
|
}
|
|
|
|
visitLiteralPrimitive(ast: LiteralPrimitive, context: any) {
|
|
if (typeof ast.value === 'string') {
|
|
this._expression += `"${ast.value.replace(Unparser._quoteRegExp, '"')}"`;
|
|
} else {
|
|
this._expression += `${ast.value}`;
|
|
}
|
|
}
|
|
|
|
visitPrefixNot(ast: PrefixNot, context: any) {
|
|
this._expression += '!';
|
|
this._visit(ast.expression);
|
|
}
|
|
|
|
visitTypeofExpression(ast: TypeofExpression, context: any) {
|
|
this._expression += 'typeof ';
|
|
this._visit(ast.expression);
|
|
}
|
|
|
|
visitVoidExpression(ast: VoidExpression, context: any) {
|
|
this._expression += 'void ';
|
|
this._visit(ast.expression);
|
|
}
|
|
|
|
visitNonNullAssert(ast: NonNullAssert, context: any) {
|
|
this._visit(ast.expression);
|
|
this._expression += '!';
|
|
}
|
|
|
|
visitSafePropertyRead(ast: SafePropertyRead, context: any) {
|
|
this._visit(ast.receiver);
|
|
this._expression += `?.${ast.name}`;
|
|
}
|
|
|
|
visitSafeKeyedRead(ast: SafeKeyedRead, context: any) {
|
|
this._visit(ast.receiver);
|
|
this._expression += '?.[';
|
|
this._visit(ast.key);
|
|
this._expression += ']';
|
|
}
|
|
|
|
visitTemplateLiteral(ast: TemplateLiteral, context: any) {
|
|
this._expression += '`';
|
|
for (let i = 0; i < ast.elements.length; i++) {
|
|
this._visit(ast.elements[i]);
|
|
const expression = i < ast.expressions.length ? ast.expressions[i] : null;
|
|
if (expression !== null) {
|
|
this._expression += '${';
|
|
this._visit(expression);
|
|
this._expression += '}';
|
|
}
|
|
}
|
|
this._expression += '`';
|
|
}
|
|
|
|
visitTemplateLiteralElement(ast: TemplateLiteralElement, context: any) {
|
|
this._expression += ast.text;
|
|
}
|
|
|
|
visitTaggedTemplateLiteral(ast: TaggedTemplateLiteral, context: any) {
|
|
this._visit(ast.tag);
|
|
this._visit(ast.template);
|
|
}
|
|
|
|
visitParenthesizedExpression(ast: ParenthesizedExpression, context: any) {
|
|
this._expression += '(';
|
|
this._visit(ast.expression);
|
|
this._expression += ')';
|
|
}
|
|
|
|
visitRegularExpressionLiteral(ast: RegularExpressionLiteral, context: any) {
|
|
this._expression += `/${ast.body}/${ast.flags || ''}`;
|
|
}
|
|
|
|
visitSpreadElement(ast: SpreadElement, context: any) {
|
|
this._expression += '...';
|
|
this._visit(ast.expression);
|
|
}
|
|
|
|
visitArrowFunction(ast: ArrowFunction, context: any) {
|
|
if (ast.parameters.length === 1) {
|
|
this._expression += ast.parameters[0].name;
|
|
} else {
|
|
this._expression += `(${ast.parameters.map((e) => e.name).join(', ')})`;
|
|
}
|
|
this._expression += ' => ';
|
|
this._visit(ast.body);
|
|
}
|
|
|
|
private _visit(ast: AST) {
|
|
ast.visit(this);
|
|
}
|
|
}
|
|
|
|
const sharedUnparser = new Unparser();
|
|
|
|
export function unparse(ast: AST): string {
|
|
return sharedUnparser.unparse(ast);
|
|
}
|
|
|
|
// [unparsed AST, original source code of AST]
|
|
type UnparsedWithSpan = [string, string];
|
|
|
|
export function unparseWithSpan(ast: ASTWithSource): UnparsedWithSpan[] {
|
|
const unparsed: UnparsedWithSpan[] = [];
|
|
const source = ast.source!;
|
|
const recursiveSpanUnparser = new (class extends RecursiveAstVisitor {
|
|
private recordUnparsed(ast: any, spanKey: string, unparsedList: UnparsedWithSpan[]) {
|
|
const span = ast[spanKey];
|
|
const prefix = spanKey === 'span' ? '' : `[${spanKey}] `;
|
|
const src = source.substring(span.start, span.end);
|
|
unparsedList.push([unparse(ast), prefix + src]);
|
|
}
|
|
|
|
override visit(ast: AST, unparsedList: UnparsedWithSpan[]) {
|
|
this.recordUnparsed(ast, 'span', unparsedList);
|
|
if (ast.hasOwnProperty('nameSpan')) {
|
|
this.recordUnparsed(ast, 'nameSpan', unparsedList);
|
|
}
|
|
if (ast.hasOwnProperty('argumentSpan')) {
|
|
this.recordUnparsed(ast, 'argumentSpan', unparsedList);
|
|
}
|
|
ast.visit(this, unparsedList);
|
|
}
|
|
})();
|
|
recursiveSpanUnparser.visitAll([ast.ast], unparsed);
|
|
return unparsed;
|
|
}
|