refactor(compiler): replace Comment nodes with leadingComments property (#38811)

Common AST formats such as TS and Babel do not use a separate
node for comments, but instead attach comments to other AST nodes.
Previously this was worked around in TS by creating a `NotEmittedStatement`
AST node to attach the comment to. But Babel does not have this facility,
so it will not be a viable approach for the linker.

This commit refactors the output AST, to remove the `CommentStmt` and
`JSDocCommentStmt` nodes. Instead statements have a collection of
`leadingComments` that are rendered/attached to the final AST nodes
when being translated or printed.

PR Close #38811
This commit is contained in:
Pete Bacon Darwin
2020-09-11 16:43:23 +01:00
committed by Misko Hevery
parent 7fb388f929
commit d795a00137
24 changed files with 427 additions and 363 deletions

View File

@ -103,7 +103,7 @@ export class ModuleWithProvidersScanner {
this.emitter.emit(ngModule, decl.getSourceFile(), ImportFlags.ForceNewImport);
const ngModuleType = new ExpressionType(ngModuleExpr);
const mwpNgType = new ExpressionType(
new ExternalExpr(Identifiers.ModuleWithProviders), /* modifiers */ null, [ngModuleType]);
new ExternalExpr(Identifiers.ModuleWithProviders), [/* modifiers */], [ngModuleType]);
dts.addTypeReplacement(decl, mwpNgType);
}

View File

@ -6,4 +6,4 @@
* found in the LICENSE file at https://angular.io/license
*/
export {Import, ImportManager, NamedImport, translateExpression, translateStatement, translateType} from './src/translator';
export {attachComments, Import, ImportManager, NamedImport, translateExpression, translateStatement, translateType} from './src/translator';

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
import {ArrayType, AssertNotNull, BinaryOperator, BinaryOperatorExpr, BuiltinType, BuiltinTypeName, CastExpr, ClassStmt, CommaExpr, CommentStmt, ConditionalExpr, DeclareFunctionStmt, DeclareVarStmt, Expression, ExpressionStatement, ExpressionType, ExpressionVisitor, ExternalExpr, FunctionExpr, IfStmt, InstantiateExpr, InvokeFunctionExpr, InvokeMethodExpr, JSDocCommentStmt, LiteralArrayExpr, LiteralExpr, LiteralMapExpr, MapType, NotExpr, ParseSourceSpan, ReadKeyExpr, ReadPropExpr, ReadVarExpr, ReturnStatement, Statement, StatementVisitor, StmtModifier, ThrowStmt, TryCatchStmt, Type, TypeofExpr, TypeVisitor, WrappedNodeExpr, WriteKeyExpr, WritePropExpr, WriteVarExpr} from '@angular/compiler';
import {ArrayType, AssertNotNull, BinaryOperator, BinaryOperatorExpr, BuiltinType, BuiltinTypeName, CastExpr, ClassStmt, CommaExpr, ConditionalExpr, DeclareFunctionStmt, DeclareVarStmt, Expression, ExpressionStatement, ExpressionType, ExpressionVisitor, ExternalExpr, FunctionExpr, IfStmt, InstantiateExpr, InvokeFunctionExpr, InvokeMethodExpr, LeadingComment, LiteralArrayExpr, LiteralExpr, LiteralMapExpr, MapType, NotExpr, ParseSourceSpan, ReadKeyExpr, ReadPropExpr, ReadVarExpr, ReturnStatement, Statement, StatementVisitor, StmtModifier, ThrowStmt, TryCatchStmt, Type, TypeofExpr, TypeVisitor, WrappedNodeExpr, WriteKeyExpr, WritePropExpr, WriteVarExpr} from '@angular/compiler';
import {LocalizedString, UnaryOperator, UnaryOperatorExpr} from '@angular/compiler/src/output/output_ast';
import * as ts from 'typescript';
@ -134,34 +134,45 @@ class ExpressionTranslatorVisitor implements ExpressionVisitor, StatementVisitor
private scriptTarget: Exclude<ts.ScriptTarget, ts.ScriptTarget.JSON>) {}
visitDeclareVarStmt(stmt: DeclareVarStmt, context: Context): ts.VariableStatement {
const nodeFlags =
((this.scriptTarget >= ts.ScriptTarget.ES2015) && stmt.hasModifier(StmtModifier.Final)) ?
ts.NodeFlags.Const :
ts.NodeFlags.None;
return ts.createVariableStatement(
undefined,
ts.createVariableDeclarationList(
[ts.createVariableDeclaration(
stmt.name, undefined,
stmt.value && stmt.value.visitExpression(this, context.withExpressionMode))],
nodeFlags));
const isConst =
this.scriptTarget >= ts.ScriptTarget.ES2015 && stmt.hasModifier(StmtModifier.Final);
const varDeclaration = ts.createVariableDeclaration(
/* name */ stmt.name,
/* type */ undefined,
/* initializer */ stmt.value?.visitExpression(this, context.withExpressionMode));
const declarationList = ts.createVariableDeclarationList(
/* declarations */[varDeclaration],
/* flags */ isConst ? ts.NodeFlags.Const : ts.NodeFlags.None);
const varStatement = ts.createVariableStatement(undefined, declarationList);
return attachComments(varStatement, stmt.leadingComments);
}
visitDeclareFunctionStmt(stmt: DeclareFunctionStmt, context: Context): ts.FunctionDeclaration {
return ts.createFunctionDeclaration(
undefined, undefined, undefined, stmt.name, undefined,
const fnDeclaration = ts.createFunctionDeclaration(
/* decorators */ undefined,
/* modifiers */ undefined,
/* asterisk */ undefined,
/* name */ stmt.name,
/* typeParameters */ undefined,
/* parameters */
stmt.params.map(param => ts.createParameter(undefined, undefined, undefined, param.name)),
undefined,
/* type */ undefined,
/* body */
ts.createBlock(
stmt.statements.map(child => child.visitStatement(this, context.withStatementMode))));
return attachComments(fnDeclaration, stmt.leadingComments);
}
visitExpressionStmt(stmt: ExpressionStatement, context: Context): ts.ExpressionStatement {
return ts.createStatement(stmt.expr.visitExpression(this, context.withStatementMode));
return attachComments(
ts.createStatement(stmt.expr.visitExpression(this, context.withStatementMode)),
stmt.leadingComments);
}
visitReturnStmt(stmt: ReturnStatement, context: Context): ts.ReturnStatement {
return ts.createReturn(stmt.value.visitExpression(this, context.withExpressionMode));
return attachComments(
ts.createReturn(stmt.value.visitExpression(this, context.withExpressionMode)),
stmt.leadingComments);
}
visitDeclareClassStmt(stmt: ClassStmt, context: Context) {
@ -174,14 +185,15 @@ class ExpressionTranslatorVisitor implements ExpressionVisitor, StatementVisitor
}
visitIfStmt(stmt: IfStmt, context: Context): ts.IfStatement {
return ts.createIf(
stmt.condition.visitExpression(this, context),
const thenBlock = ts.createBlock(
stmt.trueCase.map(child => child.visitStatement(this, context.withStatementMode)));
const elseBlock = stmt.falseCase.length > 0 ?
ts.createBlock(
stmt.trueCase.map(child => child.visitStatement(this, context.withStatementMode))),
stmt.falseCase.length > 0 ?
ts.createBlock(stmt.falseCase.map(
child => child.visitStatement(this, context.withStatementMode))) :
undefined);
stmt.falseCase.map(child => child.visitStatement(this, context.withStatementMode))) :
undefined;
const ifStatement =
ts.createIf(stmt.condition.visitExpression(this, context), thenBlock, elseBlock);
return attachComments(ifStatement, stmt.leadingComments);
}
visitTryCatchStmt(stmt: TryCatchStmt, context: Context) {
@ -189,25 +201,9 @@ class ExpressionTranslatorVisitor implements ExpressionVisitor, StatementVisitor
}
visitThrowStmt(stmt: ThrowStmt, context: Context): ts.ThrowStatement {
return ts.createThrow(stmt.error.visitExpression(this, context.withExpressionMode));
}
visitCommentStmt(stmt: CommentStmt, context: Context): ts.NotEmittedStatement {
const commentStmt = ts.createNotEmittedStatement(ts.createLiteral(''));
ts.addSyntheticLeadingComment(
commentStmt,
stmt.multiline ? ts.SyntaxKind.MultiLineCommentTrivia :
ts.SyntaxKind.SingleLineCommentTrivia,
stmt.comment, /** hasTrailingNewLine */ false);
return commentStmt;
}
visitJSDocCommentStmt(stmt: JSDocCommentStmt, context: Context): ts.NotEmittedStatement {
const commentStmt = ts.createNotEmittedStatement(ts.createLiteral(''));
const text = stmt.toString();
const kind = ts.SyntaxKind.MultiLineCommentTrivia;
ts.setSyntheticLeadingComments(commentStmt, [{kind, text, pos: -1, end: -1}]);
return commentStmt;
return attachComments(
ts.createThrow(stmt.error.visitExpression(this, context.withExpressionMode)),
stmt.leadingComments);
}
visitReadVarExpr(ast: ReadVarExpr, context: Context): ts.Identifier {
@ -784,4 +780,31 @@ function createTemplateTail(cooked: string, raw: string): ts.TemplateTail {
const node: ts.TemplateLiteralLikeNode = ts.createTemplateHead(cooked, raw);
(node.kind as ts.SyntaxKind) = ts.SyntaxKind.TemplateTail;
return node as ts.TemplateTail;
}
}
/**
* Attach the given `leadingComments` to the `statement` node.
*
* @param statement The statement that will have comments attached.
* @param leadingComments The comments to attach to the statement.
*/
export function attachComments<T extends ts.Statement>(
statement: T, leadingComments?: LeadingComment[]): T {
if (leadingComments === undefined) {
return statement;
}
for (const comment of leadingComments) {
const commentKind = comment.multiline ? ts.SyntaxKind.MultiLineCommentTrivia :
ts.SyntaxKind.SingleLineCommentTrivia;
if (comment.multiline) {
ts.addSyntheticLeadingComment(
statement, commentKind, comment.toString(), comment.trailingNewline);
} else {
for (const line of comment.text.split('\n')) {
ts.addSyntheticLeadingComment(statement, commentKind, line, comment.trailingNewline);
}
}
}
return statement;
}

View File

@ -245,7 +245,8 @@ export class Environment {
*/
referenceExternalType(moduleName: string, name: string, typeParams?: Type[]): ts.TypeNode {
const external = new ExternalExpr({moduleName, name});
return translateType(new ExpressionType(external, null, typeParams), this.importManager);
return translateType(
new ExpressionType(external, [/* modifiers */], typeParams), this.importManager);
}
getPreludeStatements(): ts.Statement[] {

View File

@ -6,10 +6,10 @@
* found in the LICENSE file at https://angular.io/license
*/
import {AssertNotNull, BinaryOperator, BinaryOperatorExpr, BuiltinMethod, BuiltinVar, CastExpr, ClassStmt, CommaExpr, CommentStmt, ConditionalExpr, DeclareFunctionStmt, DeclareVarStmt, ExpressionStatement, ExpressionVisitor, ExternalExpr, ExternalReference, FunctionExpr, IfStmt, InstantiateExpr, InvokeFunctionExpr, InvokeMethodExpr, JSDocCommentStmt, LiteralArrayExpr, LiteralExpr, LiteralMapExpr, NotExpr, ParseSourceFile, ParseSourceSpan, PartialModule, ReadKeyExpr, ReadPropExpr, ReadVarExpr, ReturnStatement, Statement, StatementVisitor, StmtModifier, ThrowStmt, TryCatchStmt, TypeofExpr, WrappedNodeExpr, WriteKeyExpr, WritePropExpr, WriteVarExpr} from '@angular/compiler';
import {LocalizedString, UnaryOperator, UnaryOperatorExpr} from '@angular/compiler/src/output/output_ast';
import {AssertNotNull, BinaryOperator, BinaryOperatorExpr, BuiltinMethod, BuiltinVar, CastExpr, ClassStmt, CommaExpr, ConditionalExpr, DeclareFunctionStmt, DeclareVarStmt, ExpressionStatement, ExpressionVisitor, ExternalExpr, ExternalReference, FunctionExpr, IfStmt, InstantiateExpr, InvokeFunctionExpr, InvokeMethodExpr, LeadingComment, leadingComment, LiteralArrayExpr, LiteralExpr, LiteralMapExpr, LocalizedString, NotExpr, ParseSourceFile, ParseSourceSpan, PartialModule, ReadKeyExpr, ReadPropExpr, ReadVarExpr, ReturnStatement, Statement, StatementVisitor, StmtModifier, ThrowStmt, TryCatchStmt, TypeofExpr, UnaryOperator, UnaryOperatorExpr, WrappedNodeExpr, WriteKeyExpr, WritePropExpr, WriteVarExpr} from '@angular/compiler';
import * as ts from 'typescript';
import {attachComments} from '../ngtsc/translator';
import {error} from './util';
export interface Node {
@ -31,30 +31,25 @@ export class TypeScriptNodeEmitter {
// stmts.
const statements: any[] = [].concat(
...stmts.map(stmt => stmt.visitStatement(converter, null)).filter(stmt => stmt != null));
const preambleStmts: ts.Statement[] = [];
if (preamble) {
const commentStmt = this.createCommentStatement(sourceFile, preamble);
preambleStmts.push(commentStmt);
}
const sourceStatements =
[...preambleStmts, ...converter.getReexports(), ...converter.getImports(), ...statements];
[...converter.getReexports(), ...converter.getImports(), ...statements];
if (preamble) {
// We always attach the preamble comment to a `NotEmittedStatement` node, because tsickle uses
// this node type as a marker of the preamble to ensure that it adds its own new nodes after
// the preamble.
const preambleCommentHolder = ts.createNotEmittedStatement(sourceFile);
// Preamble comments are passed through as-is, which means that they must already contain a
// leading `*` if they should be a JSDOC comment.
ts.addSyntheticLeadingComment(
preambleCommentHolder, ts.SyntaxKind.MultiLineCommentTrivia, preamble,
/* hasTrailingNewline */ true);
sourceStatements.unshift(preambleCommentHolder);
}
converter.updateSourceMap(sourceStatements);
const newSourceFile = ts.updateSourceFileNode(sourceFile, sourceStatements);
return [newSourceFile, converter.getNodeMap()];
}
/** Creates a not emitted statement containing the given comment. */
createCommentStatement(sourceFile: ts.SourceFile, comment: string): ts.Statement {
if (comment.startsWith('/*') && comment.endsWith('*/')) {
comment = comment.substr(2, comment.length - 4);
}
const commentStmt = ts.createNotEmittedStatement(sourceFile);
ts.setSyntheticLeadingComments(
commentStmt,
[{kind: ts.SyntaxKind.MultiLineCommentTrivia, text: comment, pos: -1, end: -1}]);
ts.setEmitFlags(commentStmt, ts.EmitFlags.CustomPrologue);
return commentStmt;
}
}
/**
@ -288,10 +283,13 @@ export class NodeEmitterVisitor implements StatementVisitor, ExpressionVisitor {
recordLastSourceRange();
}
private record<T extends ts.Node>(ngNode: Node, tsNode: T|null): RecordedNode<T> {
private postProcess<T extends ts.Node>(ngNode: Node, tsNode: T|null): RecordedNode<T> {
if (tsNode && !this._nodeMap.has(tsNode)) {
this._nodeMap.set(tsNode, ngNode);
}
if (tsNode !== null && ngNode instanceof Statement) {
attachComments(tsNode as unknown as ts.Statement, ngNode.leadingComments);
}
return tsNode as RecordedNode<T>;
}
@ -347,19 +345,19 @@ export class NodeEmitterVisitor implements StatementVisitor, ExpressionVisitor {
// Note: We need to add an explicit variable and export declaration so that
// the variable can be referred in the same file as well.
const tsVarStmt =
this.record(stmt, ts.createVariableStatement(/* modifiers */[], varDeclList));
const exportStmt = this.record(
this.postProcess(stmt, ts.createVariableStatement(/* modifiers */[], varDeclList));
const exportStmt = this.postProcess(
stmt,
ts.createExportDeclaration(
/*decorators*/ undefined, /*modifiers*/ undefined,
ts.createNamedExports([ts.createExportSpecifier(stmt.name, stmt.name)])));
return [tsVarStmt, exportStmt];
}
return this.record(stmt, ts.createVariableStatement(this.getModifiers(stmt), varDeclList));
return this.postProcess(stmt, ts.createVariableStatement(this.getModifiers(stmt), varDeclList));
}
visitDeclareFunctionStmt(stmt: DeclareFunctionStmt) {
return this.record(
return this.postProcess(
stmt,
ts.createFunctionDeclaration(
/* decorators */ undefined, this.getModifiers(stmt),
@ -372,11 +370,11 @@ export class NodeEmitterVisitor implements StatementVisitor, ExpressionVisitor {
}
visitExpressionStmt(stmt: ExpressionStatement) {
return this.record(stmt, ts.createStatement(stmt.expr.visitExpression(this, null)));
return this.postProcess(stmt, ts.createStatement(stmt.expr.visitExpression(this, null)));
}
visitReturnStmt(stmt: ReturnStatement) {
return this.record(
return this.postProcess(
stmt, ts.createReturn(stmt.value ? stmt.value.visitExpression(this, null) : undefined));
}
@ -434,7 +432,7 @@ export class NodeEmitterVisitor implements StatementVisitor, ExpressionVisitor {
/* decorators */ undefined, /* modifiers */ undefined,
/* dotDotDotToken */ undefined, p.name)),
/* type */ undefined, this._visitStatements(method.body)));
return this.record(
return this.postProcess(
stmt,
ts.createClassDeclaration(
/* decorators */ undefined, modifiers, stmt.name, /* typeParameters*/ undefined,
@ -446,7 +444,7 @@ export class NodeEmitterVisitor implements StatementVisitor, ExpressionVisitor {
}
visitIfStmt(stmt: IfStmt) {
return this.record(
return this.postProcess(
stmt,
ts.createIf(
stmt.condition.visitExpression(this, null), this._visitStatements(stmt.trueCase),
@ -455,7 +453,7 @@ export class NodeEmitterVisitor implements StatementVisitor, ExpressionVisitor {
}
visitTryCatchStmt(stmt: TryCatchStmt): RecordedNode<ts.TryStatement> {
return this.record(
return this.postProcess(
stmt,
ts.createTry(
this._visitStatements(stmt.bodyStmts),
@ -474,64 +472,46 @@ export class NodeEmitterVisitor implements StatementVisitor, ExpressionVisitor {
}
visitThrowStmt(stmt: ThrowStmt) {
return this.record(stmt, ts.createThrow(stmt.error.visitExpression(this, null)));
}
visitCommentStmt(stmt: CommentStmt, sourceFile: ts.SourceFile) {
const text = stmt.multiline ? ` ${stmt.comment} ` : ` ${stmt.comment}`;
return this.createCommentStmt(text, stmt.multiline, sourceFile);
}
visitJSDocCommentStmt(stmt: JSDocCommentStmt, sourceFile: ts.SourceFile) {
return this.createCommentStmt(stmt.toString(), true, sourceFile);
}
private createCommentStmt(text: string, multiline: boolean, sourceFile: ts.SourceFile):
ts.NotEmittedStatement {
const commentStmt = ts.createNotEmittedStatement(sourceFile);
const kind =
multiline ? ts.SyntaxKind.MultiLineCommentTrivia : ts.SyntaxKind.SingleLineCommentTrivia;
ts.setSyntheticLeadingComments(commentStmt, [{kind, text, pos: -1, end: -1}]);
return commentStmt;
return this.postProcess(stmt, ts.createThrow(stmt.error.visitExpression(this, null)));
}
// ExpressionVisitor
visitWrappedNodeExpr(expr: WrappedNodeExpr<any>) {
return this.record(expr, expr.node);
return this.postProcess(expr, expr.node);
}
visitTypeofExpr(expr: TypeofExpr) {
const typeOf = ts.createTypeOf(expr.expr.visitExpression(this, null));
return this.record(expr, typeOf);
return this.postProcess(expr, typeOf);
}
// ExpressionVisitor
visitReadVarExpr(expr: ReadVarExpr) {
switch (expr.builtin) {
case BuiltinVar.This:
return this.record(expr, ts.createIdentifier(METHOD_THIS_NAME));
return this.postProcess(expr, ts.createIdentifier(METHOD_THIS_NAME));
case BuiltinVar.CatchError:
return this.record(expr, ts.createIdentifier(CATCH_ERROR_NAME));
return this.postProcess(expr, ts.createIdentifier(CATCH_ERROR_NAME));
case BuiltinVar.CatchStack:
return this.record(expr, ts.createIdentifier(CATCH_STACK_NAME));
return this.postProcess(expr, ts.createIdentifier(CATCH_STACK_NAME));
case BuiltinVar.Super:
return this.record(expr, ts.createSuper());
return this.postProcess(expr, ts.createSuper());
}
if (expr.name) {
return this.record(expr, ts.createIdentifier(expr.name));
return this.postProcess(expr, ts.createIdentifier(expr.name));
}
throw Error(`Unexpected ReadVarExpr form`);
}
visitWriteVarExpr(expr: WriteVarExpr): RecordedNode<ts.BinaryExpression> {
return this.record(
return this.postProcess(
expr,
ts.createAssignment(
ts.createIdentifier(expr.name), expr.value.visitExpression(this, null)));
}
visitWriteKeyExpr(expr: WriteKeyExpr): RecordedNode<ts.BinaryExpression> {
return this.record(
return this.postProcess(
expr,
ts.createAssignment(
ts.createElementAccess(
@ -540,7 +520,7 @@ export class NodeEmitterVisitor implements StatementVisitor, ExpressionVisitor {
}
visitWritePropExpr(expr: WritePropExpr): RecordedNode<ts.BinaryExpression> {
return this.record(
return this.postProcess(
expr,
ts.createAssignment(
ts.createPropertyAccess(expr.receiver.visitExpression(this, null), expr.name),
@ -549,7 +529,7 @@ export class NodeEmitterVisitor implements StatementVisitor, ExpressionVisitor {
visitInvokeMethodExpr(expr: InvokeMethodExpr): RecordedNode<ts.CallExpression> {
const methodName = getMethodName(expr);
return this.record(
return this.postProcess(
expr,
ts.createCall(
ts.createPropertyAccess(expr.receiver.visitExpression(this, null), methodName),
@ -557,7 +537,7 @@ export class NodeEmitterVisitor implements StatementVisitor, ExpressionVisitor {
}
visitInvokeFunctionExpr(expr: InvokeFunctionExpr): RecordedNode<ts.CallExpression> {
return this.record(
return this.postProcess(
expr,
ts.createCall(
expr.fn.visitExpression(this, null), /* typeArguments */ undefined,
@ -565,7 +545,7 @@ export class NodeEmitterVisitor implements StatementVisitor, ExpressionVisitor {
}
visitInstantiateExpr(expr: InstantiateExpr): RecordedNode<ts.NewExpression> {
return this.record(
return this.postProcess(
expr,
ts.createNew(
expr.classExpr.visitExpression(this, null), /* typeArguments */ undefined,
@ -573,7 +553,7 @@ export class NodeEmitterVisitor implements StatementVisitor, ExpressionVisitor {
}
visitLiteralExpr(expr: LiteralExpr) {
return this.record(expr, createLiteral(expr.value));
return this.postProcess(expr, createLiteral(expr.value));
}
visitLocalizedString(expr: LocalizedString, context: any) {
@ -581,12 +561,12 @@ export class NodeEmitterVisitor implements StatementVisitor, ExpressionVisitor {
}
visitExternalExpr(expr: ExternalExpr) {
return this.record(expr, this._visitIdentifier(expr.value));
return this.postProcess(expr, this._visitIdentifier(expr.value));
}
visitConditionalExpr(expr: ConditionalExpr): RecordedNode<ts.ParenthesizedExpression> {
// TODO {chuckj}: Review use of ! on falseCase. Should it be non-nullable?
return this.record(
return this.postProcess(
expr,
ts.createParen(ts.createConditional(
expr.condition.visitExpression(this, null), expr.trueCase.visitExpression(this, null),
@ -594,7 +574,7 @@ export class NodeEmitterVisitor implements StatementVisitor, ExpressionVisitor {
}
visitNotExpr(expr: NotExpr): RecordedNode<ts.PrefixUnaryExpression> {
return this.record(
return this.postProcess(
expr,
ts.createPrefix(
ts.SyntaxKind.ExclamationToken, expr.condition.visitExpression(this, null)));
@ -609,7 +589,7 @@ export class NodeEmitterVisitor implements StatementVisitor, ExpressionVisitor {
}
visitFunctionExpr(expr: FunctionExpr) {
return this.record(
return this.postProcess(
expr,
ts.createFunctionExpression(
/* modifiers */ undefined, /* astriskToken */ undefined,
@ -636,7 +616,7 @@ export class NodeEmitterVisitor implements StatementVisitor, ExpressionVisitor {
throw new Error(`Unknown operator: ${expr.operator}`);
}
const binary = ts.createPrefix(unaryOperator, expr.expr.visitExpression(this, null));
return this.record(expr, expr.parens ? ts.createParen(binary) : binary);
return this.postProcess(expr, expr.parens ? ts.createParen(binary) : binary);
}
visitBinaryOperatorExpr(expr: BinaryOperatorExpr):
@ -696,28 +676,28 @@ export class NodeEmitterVisitor implements StatementVisitor, ExpressionVisitor {
}
const binary = ts.createBinary(
expr.lhs.visitExpression(this, null), binaryOperator, expr.rhs.visitExpression(this, null));
return this.record(expr, expr.parens ? ts.createParen(binary) : binary);
return this.postProcess(expr, expr.parens ? ts.createParen(binary) : binary);
}
visitReadPropExpr(expr: ReadPropExpr): RecordedNode<ts.PropertyAccessExpression> {
return this.record(
return this.postProcess(
expr, ts.createPropertyAccess(expr.receiver.visitExpression(this, null), expr.name));
}
visitReadKeyExpr(expr: ReadKeyExpr): RecordedNode<ts.ElementAccessExpression> {
return this.record(
return this.postProcess(
expr,
ts.createElementAccess(
expr.receiver.visitExpression(this, null), expr.index.visitExpression(this, null)));
}
visitLiteralArrayExpr(expr: LiteralArrayExpr): RecordedNode<ts.ArrayLiteralExpression> {
return this.record(
return this.postProcess(
expr, ts.createArrayLiteral(expr.entries.map(entry => entry.visitExpression(this, null))));
}
visitLiteralMapExpr(expr: LiteralMapExpr): RecordedNode<ts.ObjectLiteralExpression> {
return this.record(
return this.postProcess(
expr,
ts.createObjectLiteral(expr.entries.map(
entry => ts.createPropertyAssignment(
@ -728,7 +708,7 @@ export class NodeEmitterVisitor implements StatementVisitor, ExpressionVisitor {
}
visitCommaExpr(expr: CommaExpr): RecordedNode<ts.Expression> {
return this.record(
return this.postProcess(
expr,
expr.parts.map(e => e.visitExpression(this, null))
.reduce<ts.Expression|null>(
@ -773,7 +753,6 @@ export class NodeEmitterVisitor implements StatementVisitor, ExpressionVisitor {
}
}
function getMethodName(methodRef: {name: string|null; builtin: BuiltinMethod | null}): string {
if (methodRef.name) {
return methodRef.name;

View File

@ -10,15 +10,15 @@ import {GeneratedFile} from '@angular/compiler';
import * as ts from 'typescript';
import {TypeScriptNodeEmitter} from './node_emitter';
import {GENERATED_FILES} from './util';
import {GENERATED_FILES, stripComment} from './util';
function getPreamble(original: string) {
return `/**
return `*
* @fileoverview This file was generated by the Angular template compiler. Do not edit.
* ${original}
* @suppress {suspiciousCode,uselessCode,missingProperties,missingOverride,checkTypes,extraRequire}
* tslint:disable
*/`;
`;
}
/**
@ -41,9 +41,6 @@ export function getAngularEmitterTransformFactory(
if (orig) originalComment = getFileoverviewComment(orig);
const preamble = getPreamble(originalComment);
if (g && g.stmts) {
const orig = program.getSourceFile(g.srcFileUrl);
let originalComment = '';
if (orig) originalComment = getFileoverviewComment(orig);
const [newSourceFile] = emitter.updateSourceFile(sourceFile, g.stmts, preamble);
return newSourceFile;
} else if (GENERATED_FILES.test(sourceFile.fileName)) {
@ -51,8 +48,11 @@ export function getAngularEmitterTransformFactory(
// and various minutiae.
// Clear out the source file entirely, only including the preamble comment, so that
// ngc produces an empty .js file.
return ts.updateSourceFileNode(
sourceFile, [emitter.createCommentStatement(sourceFile, preamble)]);
const commentStmt = ts.createNotEmittedStatement(sourceFile);
ts.addSyntheticLeadingComment(
commentStmt, ts.SyntaxKind.MultiLineCommentTrivia, preamble,
/* hasTrailingNewline */ true);
return ts.updateSourceFileNode(sourceFile, [commentStmt]);
}
return sourceFile;
};
@ -75,5 +75,6 @@ function getFileoverviewComment(sourceFile: ts.SourceFile): string {
const commentText = sourceFile.getFullText().substring(comment.pos, comment.end);
// Closure Compiler ignores @suppress and similar if the comment contains @license.
if (commentText.indexOf('@license') !== -1) return '';
return commentText.replace(/^\/\*\*/, '').replace(/ ?\*\/$/, '');
// Also remove any leading `* ` from the first line in case it was a JSDOC comment
return stripComment(commentText).replace(/^\*\s+/, '');
}

View File

@ -94,3 +94,12 @@ export function ngToTsDiagnostic(ng: Diagnostic): ts.Diagnostic {
length,
};
}
/**
* Strip multiline comment start and end markers from the `commentText` string.
*
* This will also strip the JSDOC comment start marker (`/**`).
*/
export function stripComment(commentText: string): string {
return commentText.replace(/^\/\*\*?/, '').replace(/\*\/$/, '').trim();
}