angular/packages/compiler-cli/src/transformers/node_emitter_transform.ts
Martin Probst 8c52088346 fix(compiler-cli): merge @fileoverview comments. (#20870)
Previously, this code would unconditionally add a @fileoverview
comment to generated files, and only if the contained any code at all.

However often existing fileoverview comments should be copied from the
file the generated file was originally based off of. This allows users
to e.g. include Closure Compiler directives in their original
`component.ts` file, which will then automaticallly also apply to code
generated from it.

This special cases `@license` comments, as Closure disregards directives
in comments containing `@license`.

PR Close #20870
2017-12-12 11:37:55 -08:00

80 lines
3.5 KiB
TypeScript

/**
* @license
* Copyright Google Inc. 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.io/license
*/
import {GeneratedFile} from '@angular/compiler';
import * as ts from 'typescript';
import {TypeScriptNodeEmitter} from './node_emitter';
import {GENERATED_FILES} from './util';
function getPreamble(original: string) {
return `/**
* @fileoverview This file was generated by the Angular template compiler. Do not edit.
* ${original}
* @suppress {suspiciousCode,uselessCode,missingProperties,missingOverride,checkTypes}
* tslint:disable
*/`;
}
/**
* Returns a transformer that does two things for generated files (ngfactory etc):
* - adds a fileoverview JSDoc comment containing Closure Compiler specific "suppress"ions in JSDoc.
* The new comment will contain any fileoverview comment text from the original source file this
* file was generated from.
* - updates generated files that are not in the given map of generatedFiles to have an empty
* list of statements as their body.
*/
export function getAngularEmitterTransformFactory(
generatedFiles: Map<string, GeneratedFile>, program: ts.Program): () =>
(sourceFile: ts.SourceFile) => ts.SourceFile {
return function() {
const emitter = new TypeScriptNodeEmitter();
return function(sourceFile: ts.SourceFile): ts.SourceFile {
const g = generatedFiles.get(sourceFile.fileName);
const orig = g && program.getSourceFile(g.srcFileUrl);
let originalComment = '';
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)) {
// The file should be empty, but emitter.updateSourceFile would still add imports
// 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)]);
}
return sourceFile;
};
};
}
/**
* Parses and returns the comment text (without start and end markers) of a \@fileoverview comment
* in the given source file. Returns the empty string if no such comment can be found.
*/
function getFileoverviewComment(sourceFile: ts.SourceFile): string {
const trivia = sourceFile.getFullText().substring(0, sourceFile.getStart());
const leadingComments = ts.getLeadingCommentRanges(trivia, 0);
if (!leadingComments || leadingComments.length === 0) return '';
const comment = leadingComments[0];
if (comment.kind !== ts.SyntaxKind.MultiLineCommentTrivia) return '';
// Only comments separated with a \n\n from the file contents are considered file-level comments
// in TypeScript.
if (sourceFile.getFullText().substring(comment.end, comment.end + 2) !== '\n\n') return '';
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(/ ?\*\/$/, '');
}