diff --git a/packages/bazel/src/ngc-wrapped/index.ts b/packages/bazel/src/ngc-wrapped/index.ts index c0228fe4d8..e78b106454 100644 --- a/packages/bazel/src/ngc-wrapped/index.ts +++ b/packages/bazel/src/ngc-wrapped/index.ts @@ -184,7 +184,7 @@ export function compile({allDepsCompiledWithBazel = true, useManifestPathsAsModu } // Detect from compilerOpts whether the entrypoint is being invoked in Ivy mode. - const isInIvyMode = compilerOpts.enableIvy === 'ngtsc'; + const isInIvyMode = !!compilerOpts.enableIvy; // Disable downleveling and Closure annotation if in Ivy mode. if (isInIvyMode) { @@ -246,9 +246,18 @@ export function compile({allDepsCompiledWithBazel = true, useManifestPathsAsModu files, compilerOpts, bazelOpts, tsHost, fileLoader, generatedFileModuleResolver); } - // Also need to disable decorator downleveling in the BazelHost in Ivy mode. if (isInIvyMode) { + // Also need to disable decorator downleveling in the BazelHost in Ivy mode. bazelHost.transformDecorators = false; + + const delegate = bazelHost.shouldSkipTsickleProcessing.bind(bazelHost); + bazelHost.shouldSkipTsickleProcessing = (fileName: string) => { + // The base implementation of shouldSkipTsickleProcessing checks whether `fileName` is part of + // the original `srcs[]`. For Angular (Ivy) compilations, ngfactory/ngsummary files that are + // shims for original .ts files in the program should be treated identically. Thus, strip the + // '.ngfactory' or '.ngsummary' part of the filename away before calling the delegate. + return delegate(fileName.replace(/\.(ngfactory|ngsummary)\.ts$/, '.ts')); + }; } // Prevent tsickle adding any types at all if we don't want closure compiler annotations. diff --git a/packages/compiler-cli/src/main.ts b/packages/compiler-cli/src/main.ts index 64f03358fb..88b2fc37b5 100644 --- a/packages/compiler-cli/src/main.ts +++ b/packages/compiler-cli/src/main.ts @@ -103,8 +103,9 @@ function createEmitCallback(options: api.CompilerOptions): api.TsEmitCallback|un tsickle.TsickleHost, 'shouldSkipTsickleProcessing'|'pathToModuleName'| 'shouldIgnoreWarningsForPath'|'fileNameToModuleId'|'googmodule'|'untyped'| 'convertIndexImportShorthand'|'transformDecorators'|'transformTypesToClosure'> = { - shouldSkipTsickleProcessing: (fileName) => - /\.d\.ts$/.test(fileName) || GENERATED_FILES.test(fileName), + shouldSkipTsickleProcessing: (fileName) => /\.d\.ts$/.test(fileName) || + // View Engine's generated files were never intended to be processed with tsickle. + (!options.enableIvy && GENERATED_FILES.test(fileName)), pathToModuleName: (context, importPath) => '', shouldIgnoreWarningsForPath: (filePath) => false, fileNameToModuleId: (fileName) => fileName, diff --git a/packages/compiler-cli/src/ngtsc/shims/src/factory_generator.ts b/packages/compiler-cli/src/ngtsc/shims/src/factory_generator.ts index 9b76806f24..426687636c 100644 --- a/packages/compiler-cli/src/ngtsc/shims/src/factory_generator.ts +++ b/packages/compiler-cli/src/ngtsc/shims/src/factory_generator.ts @@ -56,21 +56,16 @@ export class FactoryGenerator implements ShimGenerator { .map(decl => decl.name !.text); + let sourceText = ''; + // If there is a top-level comment in the original file, copy it over at the top of the // generated factory file. This is important for preserving any load-bearing jsdoc comments. - let comment: string = ''; - if (original.statements.length > 0) { - const firstStatement = original.statements[0]; - // Must pass SourceFile to getLeadingTriviaWidth() and getFullText(), otherwise it'll try to - // get SourceFile by recursively looking up the parent of the Node and fail, - // because parent is undefined. - const leadingTriviaWidth = firstStatement.getLeadingTriviaWidth(original); - if (leadingTriviaWidth > 0) { - comment = firstStatement.getFullText(original).substr(0, leadingTriviaWidth); - } + const leadingComment = getFileoverviewComment(original); + if (leadingComment !== null) { + // Leading comments must be separated from the rest of the contents by a blank line. + sourceText = leadingComment + '\n\n'; } - let sourceText = comment; if (symbolNames.length > 0) { // For each symbol name, generate a constant export of the corresponding NgFactory. // This will encompass a lot of symbols which don't need factories, but that's okay @@ -248,3 +243,36 @@ function transformFactorySourceFile( return file; } + + +/** + * Parses and returns the comment text of a \@fileoverview comment in the given source file. + */ +function getFileoverviewComment(sourceFile: ts.SourceFile): string|null { + const text = sourceFile.getFullText(); + const trivia = text.substring(0, sourceFile.getStart()); + + const leadingComments = ts.getLeadingCommentRanges(trivia, 0); + if (!leadingComments || leadingComments.length === 0) { + return null; + } + + const comment = leadingComments[0]; + if (comment.kind !== ts.SyntaxKind.MultiLineCommentTrivia) { + return null; + } + + // Only comments separated with a \n\n from the file contents are considered file-level comments + // in TypeScript. + if (text.substring(comment.end, comment.end + 2) !== '\n\n') { + return null; + } + + const commentText = text.substring(comment.pos, comment.end); + // Closure Compiler ignores @suppress and similar if the comment contains @license. + if (commentText.indexOf('@license') !== -1) { + return null; + } + + return commentText; +} diff --git a/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts b/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts index 94958bd920..9921e67f1a 100644 --- a/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts +++ b/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts @@ -3322,21 +3322,6 @@ runInEachFileSystem(os => { expect(ngfactoryContents).toContain(`i0.ɵNgModuleFactory`); }); - it('should copy a top-level comment into a factory stub', () => { - env.tsconfig({'allowEmptyCodegenFiles': true}); - - env.write('test.ts', `/** I am a top-level comment. */ - import {NgModule} from '@angular/core'; - - @NgModule({}) - export class TestModule {} - `); - env.driveMain(); - - const factoryContents = env.getContents('test.ngfactory.js'); - expect(factoryContents).toMatch(/^\/\*\* I am a top-level comment\. \*\//); - }); - it('should be able to compile an app using the factory shim', () => { env.tsconfig({'allowEmptyCodegenFiles': true}); @@ -3376,6 +3361,48 @@ runInEachFileSystem(os => { export var TestModuleNgFactory = new i0.NgModuleFactory(TestModule); `)); }); + + describe('file-level comments', () => { + it('should copy a top-level comment into a factory stub', () => { + env.tsconfig({'allowEmptyCodegenFiles': true}); + + env.write('test.ts', `/** I am a top-level comment. */ + + import {NgModule} from '@angular/core'; + + @NgModule({}) + export class TestModule {} + `); + env.driveMain(); + + const factoryContents = env.getContents('test.ngfactory.js'); + expect(factoryContents).toContain(`/** I am a top-level comment. */\n`); + }); + + it('should not copy a non-file level comment into a factory stub', () => { + env.tsconfig({'allowEmptyCodegenFiles': true}); + + env.write('test.ts', `/** I am a top-level comment, but not for the file. */ + export const TEST = true; + `); + env.driveMain(); + + const factoryContents = env.getContents('test.ngfactory.js'); + expect(factoryContents).not.toContain('top-level comment'); + }); + + it('should not copy a file level comment with an @license into a factory stub', () => { + env.tsconfig({'allowEmptyCodegenFiles': true}); + + env.write('test.ts', `/** @license I am a top-level comment, but have a license. */ + export const TEST = true; + `); + env.driveMain(); + + const factoryContents = env.getContents('test.ngfactory.js'); + expect(factoryContents).not.toContain('top-level comment'); + }); + }); }); @@ -4801,6 +4828,31 @@ runInEachFileSystem(os => { expect(trim(jsContents).startsWith(trim(fileoverview))).toBeTruthy(); }); + it('should be produced for generated factory files', () => { + env.tsconfig({ + 'annotateForClosureCompiler': true, + 'generateNgFactoryShims': true, + }); + env.write(`test.ts`, ` + import {Component} from '@angular/core'; + + @Component({ + template: '
', + }) + export class SomeComp {} + `); + + env.driveMain(); + const jsContents = env.getContents('test.ngfactory.js'); + const fileoverview = ` + /** + * @fileoverview added by tsickle + * @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc + */ + `; + expect(trim(jsContents).startsWith(trim(fileoverview))).toBeTruthy(); + }); + it('should always be at the very beginning of a script (if placed above imports)', () => { env.tsconfig({ 'annotateForClosureCompiler': true,