diff --git a/integration/ngcc/yarn.lock b/integration/ngcc/yarn.lock index ca538d725fe4..7f24bb30513c 100644 --- a/integration/ngcc/yarn.lock +++ b/integration/ngcc/yarn.lock @@ -3,7 +3,7 @@ "@angular/animations@file:../../dist/packages-dist/animations": - version "7.1.0-beta.1" + version "7.1.0" dependencies: tslib "^1.9.0" @@ -17,14 +17,14 @@ parse5 "^5.0.0" "@angular/common@file:../../dist/packages-dist/common": - version "7.1.0-beta.1" + version "7.1.0" dependencies: tslib "^1.9.0" "@angular/compiler-cli@file:../../dist/packages-dist/compiler-cli": - version "7.1.0-beta.1" + version "7.1.0" dependencies: - canonical-path "0.0.2" + canonical-path "1.0.0" chokidar "^1.4.2" convert-source-map "^1.5.1" dependency-graph "^0.7.2" @@ -33,25 +33,26 @@ reflect-metadata "^0.1.2" shelljs "^0.8.1" source-map "^0.6.1" + tslib "^1.9.0" yargs "9.0.1" "@angular/compiler@file:../../dist/packages-dist/compiler": - version "7.1.0-beta.1" + version "7.1.0" dependencies: tslib "^1.9.0" "@angular/core@file:../../dist/packages-dist/core": - version "7.1.0-beta.1" + version "7.1.0" dependencies: tslib "^1.9.0" "@angular/forms@file:../../dist/packages-dist/forms": - version "7.1.0-beta.1" + version "7.1.0" dependencies: tslib "^1.9.0" "@angular/http@file:../../dist/packages-dist/http": - version "7.1.0-beta.1" + version "7.1.0" dependencies: tslib "^1.9.0" @@ -65,17 +66,17 @@ parse5 "^5.0.0" "@angular/platform-browser-dynamic@file:../../dist/packages-dist/platform-browser-dynamic": - version "7.1.0-beta.1" + version "7.1.0" dependencies: tslib "^1.9.0" "@angular/platform-browser@file:../../dist/packages-dist/platform-browser": - version "7.1.0-beta.1" + version "7.1.0" dependencies: tslib "^1.9.0" "@angular/router@file:../../dist/packages-dist/router": - version "7.1.0-beta.1" + version "7.1.0" dependencies: tslib "^1.9.0" @@ -533,10 +534,10 @@ camelcase@^4.1.0: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-4.1.0.tgz#d545635be1e33c542649c69173e5de6acfae34dd" integrity sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0= -canonical-path@0.0.2: - version "0.0.2" - resolved "https://registry.yarnpkg.com/canonical-path/-/canonical-path-0.0.2.tgz#e31eb937a8c93ee2a01df1839794721902874574" - integrity sha1-4x65N6jJPuKgHfGDl5RyGQKHRXQ= +canonical-path@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/canonical-path/-/canonical-path-1.0.0.tgz#fcb470c23958def85081856be7a86e904f180d1d" + integrity sha512-feylzsbDxi1gPZ1IjystzIQZagYYLvfKrSuygUCgf7z6x790VEzze5QEkdSV1U58RA7Hi0+v6fv4K54atOzATg== caseless@~0.12.0: version "0.12.0" diff --git a/packages/compiler-cli/src/ngcc/src/analysis/decoration_analyzer.ts b/packages/compiler-cli/src/ngcc/src/analysis/decoration_analyzer.ts index 75fb58bfc52a..87c0f4174f89 100644 --- a/packages/compiler-cli/src/ngcc/src/analysis/decoration_analyzer.ts +++ b/packages/compiler-cli/src/ngcc/src/analysis/decoration_analyzer.ts @@ -9,9 +9,8 @@ import {ConstantPool} from '@angular/compiler'; import * as fs from 'fs'; import * as ts from 'typescript'; -import {BaseDefDecoratorHandler, ComponentDecoratorHandler, DirectiveDecoratorHandler, InjectableDecoratorHandler, NgModuleDecoratorHandler, PipeDecoratorHandler, ResourceLoader, SelectorScopeRegistry} from '../../../ngtsc/annotations'; +import {BaseDefDecoratorHandler, ComponentDecoratorHandler, DirectiveDecoratorHandler, InjectableDecoratorHandler, NgModuleDecoratorHandler, PipeDecoratorHandler, ReferencesRegistry, ResourceLoader, SelectorScopeRegistry} from '../../../ngtsc/annotations'; import {CompileResult, DecoratorHandler} from '../../../ngtsc/transform'; - import {DecoratedClass} from '../host/decorated_class'; import {NgccReflectionHost} from '../host/ngcc_host'; import {isDefined} from '../utils'; @@ -63,13 +62,15 @@ export class DecorationAnalyzer { this.rootDirs), new DirectiveDecoratorHandler(this.typeChecker, this.host, this.scopeRegistry, this.isCore), new InjectableDecoratorHandler(this.host, this.isCore), - new NgModuleDecoratorHandler(this.typeChecker, this.host, this.scopeRegistry, this.isCore), + new NgModuleDecoratorHandler( + this.typeChecker, this.host, this.scopeRegistry, this.referencesRegistry, this.isCore), new PipeDecoratorHandler(this.typeChecker, this.host, this.scopeRegistry, this.isCore), ]; constructor( private typeChecker: ts.TypeChecker, private host: NgccReflectionHost, - private rootDirs: string[], private isCore: boolean) {} + private referencesRegistry: ReferencesRegistry, private rootDirs: string[], + private isCore: boolean) {} /** * Analyze a program to find all the decorated files should be transformed. diff --git a/packages/compiler-cli/src/ngcc/src/analysis/ngcc_references_registry.ts b/packages/compiler-cli/src/ngcc/src/analysis/ngcc_references_registry.ts new file mode 100644 index 000000000000..f4779456b605 --- /dev/null +++ b/packages/compiler-cli/src/ngcc/src/analysis/ngcc_references_registry.ts @@ -0,0 +1,49 @@ +/** + * @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 * as ts from 'typescript'; +import {ReferencesRegistry} from '../../../ngtsc/annotations'; +import {Declaration, ReflectionHost} from '../../../ngtsc/host'; +import {Reference, ResolvedReference} from '../../../ngtsc/metadata'; +import {hasNameIdentifier} from '../utils'; + +/** + * This is a place for DecoratorHandlers to register references that they + * find in their analysis of the code. + * + * This registry is used to ensure that these references are publicly exported + * from libraries that are compiled by ngcc. + */ +export class NgccReferencesRegistry implements ReferencesRegistry { + private map = new Map(); + + constructor(private host: ReflectionHost) {} + + /** + * Register one or more references in the registry. + * Only `ResolveReference` references are stored. Other types are ignored. + * @param references A collection of references to register. + */ + add(...references: Reference[]): void { + references.forEach(ref => { + // Only store resolved references. We are not interested in literals. + if (ref instanceof ResolvedReference && hasNameIdentifier(ref.node)) { + const declaration = this.host.getDeclarationOfIdentifier(ref.node.name); + if (declaration && hasNameIdentifier(declaration.node)) { + this.map.set(declaration.node.name, declaration); + } + } + }); + } + + /** + * Create and return a mapping for the registered resolved references. + * @returns A map of reference identifiers to reference declarations. + */ + getDeclarationMap(): Map { return this.map; } +} diff --git a/packages/compiler-cli/src/ngcc/src/analysis/private_declarations_analyzer.ts b/packages/compiler-cli/src/ngcc/src/analysis/private_declarations_analyzer.ts new file mode 100644 index 000000000000..26d3f839a0a8 --- /dev/null +++ b/packages/compiler-cli/src/ngcc/src/analysis/private_declarations_analyzer.ts @@ -0,0 +1,60 @@ +/** + * @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 * as ts from 'typescript'; + +import {ReferencesRegistry} from '../../../ngtsc/annotations'; +import {Declaration} from '../../../ngtsc/host'; +import {NgccReflectionHost} from '../host/ngcc_host'; +import {hasNameIdentifier, isDefined} from '../utils'; + +export interface ExportInfo { + identifier: string; + from: string; + dtsFrom: string|null; +} +export type PrivateDeclarationsAnalyses = ExportInfo[]; + +/** + * This class will analyze a program to find all the declared classes + * (i.e. on an NgModule) that are not publicly exported via an entry-point. + */ +export class PrivateDeclarationsAnalyzer { + constructor(private host: NgccReflectionHost, private referencesRegistry: ReferencesRegistry) {} + + analyzeProgram(program: ts.Program): PrivateDeclarationsAnalyses { + const rootFiles = this.getRootFiles(program); + return this.getPrivateDeclarations(rootFiles, this.referencesRegistry.getDeclarationMap()); + } + + private getRootFiles(program: ts.Program): ts.SourceFile[] { + return program.getRootFileNames().map(f => program.getSourceFile(f)).filter(isDefined); + } + + private getPrivateDeclarations( + rootFiles: ts.SourceFile[], + declarations: Map): PrivateDeclarationsAnalyses { + const privateDeclarations: Map = new Map(declarations); + rootFiles.forEach(f => { + const exports = this.host.getExportsOfModule(f); + if (exports) { + exports.forEach((declaration, exportedName) => { + if (hasNameIdentifier(declaration.node) && declaration.node.name.text === exportedName) { + privateDeclarations.delete(declaration.node.name); + } + }); + } + }); + return Array.from(privateDeclarations.keys()).map(id => { + const from = id.getSourceFile().fileName; + const declaration = privateDeclarations.get(id) !; + const dtsDeclaration = this.host.getDtsDeclarationOfClass(declaration.node); + const dtsFrom = dtsDeclaration && dtsDeclaration.getSourceFile().fileName; + return {identifier: id.text, from, dtsFrom}; + }); + } +} diff --git a/packages/compiler-cli/src/ngcc/src/host/esm2015_host.ts b/packages/compiler-cli/src/ngcc/src/host/esm2015_host.ts index a59610ee4b18..a4d12aa42042 100644 --- a/packages/compiler-cli/src/ngcc/src/host/esm2015_host.ts +++ b/packages/compiler-cli/src/ngcc/src/host/esm2015_host.ts @@ -10,7 +10,8 @@ import * as ts from 'typescript'; import {ClassMember, ClassMemberKind, CtorParameter, Decorator, Import} from '../../../ngtsc/host'; import {TypeScriptReflectionHost, reflectObjectLiteral} from '../../../ngtsc/metadata'; -import {findAll, getNameText, getOriginalSymbol, isDefined} from '../utils'; +import {BundleProgram} from '../packages/bundle_program'; +import {findAll, getNameText, isDefined} from '../utils'; import {DecoratedClass} from './decorated_class'; import {NgccReflectionHost, PRE_R3_MARKER, SwitchableVariableDeclaration, isSwitchableVariableDeclaration} from './ngcc_host'; @@ -49,13 +50,9 @@ export const CONSTRUCTOR_PARAMS = 'ctorParameters' as ts.__String; */ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements NgccReflectionHost { protected dtsClassMap: Map|null; - constructor( - protected isCore: boolean, checker: ts.TypeChecker, dtsRootFileName?: string, - dtsProgram?: ts.Program|null) { + constructor(protected isCore: boolean, checker: ts.TypeChecker, dts?: BundleProgram|null) { super(checker); - this.dtsClassMap = (dtsRootFileName && dtsProgram) ? - this.computeDtsClassMap(dtsRootFileName, dtsProgram) : - null; + this.dtsClassMap = dts && this.computeDtsClassMap(dts.path, dts.program) || null; } /** @@ -356,12 +353,7 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N throw new Error( `Cannot get the dts file for a class declaration that has no indetifier: ${declaration.getText()} in ${declaration.getSourceFile().fileName}`); } - const dtsDeclaration = this.dtsClassMap.get(declaration.name.text); - if (!dtsDeclaration) { - throw new Error( - `Unable to find matching typings (.d.ts) declaration for ${declaration.name.text} in ${declaration.getSourceFile().fileName}`); - } - return dtsDeclaration; + return this.dtsClassMap.get(declaration.name.text) || null; } } return null; @@ -853,7 +845,7 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N } /** - * Get the parmeter type and decorators for a class where the information is stored on + * Get the parameter type and decorators for a class where the information is stored on * in calls to `__decorate` helpers. * * Reflect over the helpers to find the decorators and types about each of @@ -978,7 +970,7 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N * Test whether a decorator was imported from `@angular/core`. * * Is the decorator: - * * externally imported from `@angulare/core`? + * * externally imported from `@angular/core`? * * the current hosted program is actually `@angular/core` and * - relatively internally imported; or * - not imported, from the current file. @@ -993,31 +985,37 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N } } + /** + * Extract all the class declarations from the dtsTypings program, storing them in a map + * where the key is the declared name of the class and the value is the declaration itself. + * + * It is possible for there to be multiple class declarations with the same local name. + * Only the first declaration with a given name is added to the map; subsequent classes will be + * ignored. + * + * We are most interested in classes that are publicly exported from the entry point, so these are + * added to the map first, to ensure that they are not ignored. + * + * @param dtsRootFileName The filename of the entry-point to the `dtsTypings` program. + * @param dtsProgram The program containing all the typings files. + * @returns a map of class names to class declarations. + */ protected computeDtsClassMap(dtsRootFileName: string, dtsProgram: ts.Program): Map { const dtsClassMap = new Map(); const checker = dtsProgram.getTypeChecker(); - const dtsRootFile = dtsProgram.getSourceFile(dtsRootFileName); - const rootModule = dtsRootFile && checker.getSymbolAtLocation(dtsRootFile); - const moduleExports = rootModule && checker.getExportsOfModule(rootModule); - if (moduleExports) { - moduleExports.forEach(exportedSymbol => { - if (exportedSymbol.flags & ts.SymbolFlags.Alias) { - exportedSymbol = checker.getAliasedSymbol(exportedSymbol); - } - const declaration = exportedSymbol.declarations[0]; - if (declaration && ts.isClassDeclaration(declaration)) { - const name = exportedSymbol.name; - const previousDeclaration = dtsClassMap.get(name); - if (previousDeclaration && previousDeclaration !== declaration) { - console.warn( - `Ambiguous class name ${name} in typings files: ${previousDeclaration.getSourceFile().fileName} and ${declaration.getSourceFile().fileName}`); - } else { - dtsClassMap.set(name, declaration); - } - } - }); + + // First add all the classes that are publicly exported from the entry-point + const rootFile = dtsProgram.getSourceFile(dtsRootFileName); + if (!rootFile) { + throw new Error(`The given file ${dtsRootFileName} is not part of the typings program.`); } + collectExportedClasses(checker, dtsClassMap, rootFile); + + // Now add any additional classes that are exported from individual dts files, + // but are not publicly exported from the entry-point. + dtsProgram.getSourceFiles().forEach( + sourceFile => { collectExportedClasses(checker, dtsClassMap, sourceFile); }); return dtsClassMap; } } @@ -1151,3 +1149,28 @@ function getFarLeftIdentifier(propertyAccess: ts.PropertyAccessExpression): ts.I } return ts.isIdentifier(propertyAccess.expression) ? propertyAccess.expression : null; } + +/** + * Search a source file for exported classes, storing them in the provided `dtsClassMap`. + * @param checker The typechecker for the source program. + * @param dtsClassMap The map in which to store the collected exported classes. + * @param srcFile The source file to search for exported classes. + */ +function collectExportedClasses( + checker: ts.TypeChecker, dtsClassMap: Map, + srcFile: ts.SourceFile): void { + const srcModule = srcFile && checker.getSymbolAtLocation(srcFile); + const moduleExports = srcModule && checker.getExportsOfModule(srcModule); + if (moduleExports) { + moduleExports.forEach(exportedSymbol => { + if (exportedSymbol.flags & ts.SymbolFlags.Alias) { + exportedSymbol = checker.getAliasedSymbol(exportedSymbol); + } + const declaration = exportedSymbol.valueDeclaration; + const name = exportedSymbol.name; + if (declaration && ts.isClassDeclaration(declaration) && !dtsClassMap.has(name)) { + dtsClassMap.set(name, declaration); + } + }); + } +} diff --git a/packages/compiler-cli/src/ngcc/src/main.ts b/packages/compiler-cli/src/ngcc/src/main.ts index 35f003c2f5a0..924fc42232d3 100644 --- a/packages/compiler-cli/src/ngcc/src/main.ts +++ b/packages/compiler-cli/src/ngcc/src/main.ts @@ -11,6 +11,7 @@ import * as yargs from 'yargs'; import {DependencyHost} from './packages/dependency_host'; import {DependencyResolver} from './packages/dependency_resolver'; import {EntryPointFormat} from './packages/entry_point'; +import {makeEntryPointBundle} from './packages/entry_point_bundle'; import {EntryPointFinder} from './packages/entry_point_finder'; import {Transformer} from './packages/transformer'; @@ -48,12 +49,25 @@ export function mainNgcc(args: string[]): number { try { const {entryPoints} = finder.findEntryPoints(sourcePath); entryPoints.forEach(entryPoint => { + + // Are we compiling the Angular core? + const isCore = entryPoint.name === '@angular/core'; + // We transform the d.ts typings files while transforming one of the formats. // This variable decides with which of the available formats to do this transform. // It is marginally faster to process via the flat file if available. - const dtsTranformFormat: EntryPointFormat = entryPoint.fesm2015 ? 'fesm2015' : 'esm2015'; - formats.forEach( - format => transformer.transform(entryPoint, format, format === dtsTranformFormat)); + const dtsTransformFormat: EntryPointFormat = entryPoint.fesm2015 ? 'fesm2015' : 'esm2015'; + + formats.forEach(format => { + const bundle = + makeEntryPointBundle(entryPoint, isCore, format, format === dtsTransformFormat); + if (bundle === null) { + console.warn( + `Skipping ${entryPoint.name} : ${format} (no entry point file for this format).`); + } else { + transformer.transform(entryPoint, isCore, bundle); + } + }); }); } catch (e) { console.error(e.stack); diff --git a/packages/compiler-cli/src/ngcc/src/packages/bundle.ts b/packages/compiler-cli/src/ngcc/src/packages/bundle.ts deleted file mode 100644 index 872e8ad213f1..000000000000 --- a/packages/compiler-cli/src/ngcc/src/packages/bundle.ts +++ /dev/null @@ -1,31 +0,0 @@ -/** - * @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 * as ts from 'typescript'; - -/** - * A bundle represents the currently compiled entry point format, containing - * information that is necessary for compiling @angular/core with ngcc. - */ -export interface BundleInfo { - isCore: boolean; - isFlat: boolean; - rewriteCoreImportsTo: ts.SourceFile|null; - rewriteCoreDtsImportsTo: ts.SourceFile|null; -} - -export function createBundleInfo( - isCore: boolean, rewriteCoreImportsTo: ts.SourceFile | null, - rewriteCoreDtsImportsTo: ts.SourceFile | null): BundleInfo { - return { - isCore, - isFlat: rewriteCoreImportsTo === null, - rewriteCoreImportsTo: rewriteCoreImportsTo, - rewriteCoreDtsImportsTo: rewriteCoreDtsImportsTo, - }; -} diff --git a/packages/compiler-cli/src/ngcc/src/packages/bundle_program.ts b/packages/compiler-cli/src/ngcc/src/packages/bundle_program.ts new file mode 100644 index 000000000000..9bbcd8abb88c --- /dev/null +++ b/packages/compiler-cli/src/ngcc/src/packages/bundle_program.ts @@ -0,0 +1,72 @@ +/** + * @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 {dirname, resolve} from 'canonical-path'; +import {existsSync, lstatSync, readdirSync} from 'fs'; +import * as ts from 'typescript'; + +/** +* An entry point bundle contains one or two programs, e.g. `src` and `dts`, +* that are compiled via TypeScript. +* +* To aid with processing the program, this interface exposes the program itself, +* as well as path and TS file of the entry-point to the program and the r3Symbols +* file, if appropriate. +*/ +export interface BundleProgram { + program: ts.Program; + path: string; + file: ts.SourceFile; + r3SymbolsPath: string|null; + r3SymbolsFile: ts.SourceFile|null; +} + +/** + * Create a bundle program. + */ +export function makeBundleProgram( + isCore: boolean, path: string, r3FileName: string, options: ts.CompilerOptions, + host: ts.CompilerHost): BundleProgram { + const r3SymbolsPath = isCore ? findR3SymbolsPath(dirname(path), r3FileName) : null; + const rootPaths = r3SymbolsPath ? [path, r3SymbolsPath] : [path]; + const program = ts.createProgram(rootPaths, options, host); + const file = program.getSourceFile(path) !; + const r3SymbolsFile = r3SymbolsPath && program.getSourceFile(r3SymbolsPath) || null; + + return {program, path, file, r3SymbolsPath, r3SymbolsFile}; +} + +/** + * Search the given directory hierarchy to find the path to the `r3_symbols` file. + */ +export function findR3SymbolsPath(directory: string, filename: string): string|null { + const r3SymbolsFilePath = resolve(directory, filename); + if (existsSync(r3SymbolsFilePath)) { + return r3SymbolsFilePath; + } + + const subDirectories = + readdirSync(directory) + // Not interested in hidden files + .filter(p => !p.startsWith('.')) + // Ignore node_modules + .filter(p => p !== 'node_modules') + // Only interested in directories (and only those that are not symlinks) + .filter(p => { + const stat = lstatSync(resolve(directory, p)); + return stat.isDirectory() && !stat.isSymbolicLink(); + }); + + for (const subDirectory of subDirectories) { + const r3SymbolsFilePath = findR3SymbolsPath(resolve(directory, subDirectory, ), filename); + if (r3SymbolsFilePath) { + return r3SymbolsFilePath; + } + } + + return null; +} diff --git a/packages/compiler-cli/src/ngcc/src/packages/dependency_host.ts b/packages/compiler-cli/src/ngcc/src/packages/dependency_host.ts index 9255fd5de01c..5c26e34a8bff 100644 --- a/packages/compiler-cli/src/ngcc/src/packages/dependency_host.ts +++ b/packages/compiler-cli/src/ngcc/src/packages/dependency_host.ts @@ -28,7 +28,7 @@ export class DependencyHost { from: string, resolved: Set, missing: Set, deepImports: Set, internal: Set = new Set()): void { const fromContents = fs.readFileSync(from, 'utf8'); - if (!this.hasImportOrReeportStatements(fromContents)) { + if (!this.hasImportOrReexportStatements(fromContents)) { return; } @@ -142,7 +142,7 @@ export class DependencyHost { * @returns false if there are definitely no import or re-export statements * in this file, true otherwise. */ - hasImportOrReeportStatements(source: string): boolean { + hasImportOrReexportStatements(source: string): boolean { return /(import|export)\s.+from/.test(source); } } diff --git a/packages/compiler-cli/src/ngcc/src/packages/entry_point.ts b/packages/compiler-cli/src/ngcc/src/packages/entry_point.ts index 59742bfb314b..d2fe854c0294 100644 --- a/packages/compiler-cli/src/ngcc/src/packages/entry_point.ts +++ b/packages/compiler-cli/src/ngcc/src/packages/entry_point.ts @@ -11,22 +11,26 @@ import * as fs from 'fs'; /** - * The possible values for the format of an entry-point. + * An object containing paths to the entry-points for each format. */ -export type EntryPointFormat = 'esm5' | 'fesm5' | 'esm2015' | 'fesm2015' | 'umd'; +export interface EntryPointPaths { + esm5?: string; + fesm5?: string; + esm2015?: string; + fesm2015?: string; + umd?: string; +} /** - * An object containing paths to the entry-points for each format. + * The possible values for the format of an entry-point. */ -export type EntryPointPaths = { - [Format in EntryPointFormat]?: string; -}; +export type EntryPointFormat = keyof(EntryPointPaths); /** * An object containing information about an entry-point, including paths * to each of the possible entry-point formats. */ -export type EntryPoint = EntryPointPaths & { +export interface EntryPoint extends EntryPointPaths { /** The name of the package (e.g. `@angular/core`). */ name: string; /** The path to the package that contains this entry-point. */ @@ -35,8 +39,11 @@ export type EntryPoint = EntryPointPaths & { path: string; /** The path to a typings (.d.ts) file for this entry-point. */ typings: string; -}; +} +/** + * The properties that may be loaded from the `package.json` file. + */ interface EntryPointPackageJson { name: string; fesm2015?: string; @@ -55,7 +62,7 @@ interface EntryPointPackageJson { * @param packageJsonPath the absolute path to the package.json file. * @returns JSON from the package.json file if it is valid, `null` otherwise. */ -function loadEntryPointPackage(packageJsonPath: string): {[key: string]: any}|null { +function loadEntryPointPackage(packageJsonPath: string): EntryPointPackageJson|null { try { return JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')); } catch (e) { @@ -66,13 +73,13 @@ function loadEntryPointPackage(packageJsonPath: string): {[key: string]: any}|nu } /** - * Try to get entry point info from the given path. - * @param pkgPath the absolute path to the containing npm package - * @param entryPoint the absolute path to the potential entry point. + * Try to get an entry point from the given path. + * @param packagePath the absolute path to the containing npm package + * @param entryPointPath the absolute path to the potential entry point. * @returns Info about the entry point if it is valid, `null` otherwise. */ -export function getEntryPointInfo(pkgPath: string, entryPoint: string): EntryPoint|null { - const packageJsonPath = path.resolve(entryPoint, 'package.json'); +export function getEntryPointInfo(packagePath: string, entryPointPath: string): EntryPoint|null { + const packageJsonPath = path.resolve(entryPointPath, 'package.json'); if (!fs.existsSync(packageJsonPath)) { return null; } @@ -101,33 +108,34 @@ export function getEntryPointInfo(pkgPath: string, entryPoint: string): EntryPoi return null; } - // Also we need to have a metadata.json file - const metadataPath = path.resolve(entryPoint, typings.replace(/\.d\.ts$/, '') + '.metadata.json'); + // Also there must exist a `metadata.json` file next to the typings entry-point. + const metadataPath = + path.resolve(entryPointPath, typings.replace(/\.d\.ts$/, '') + '.metadata.json'); if (!fs.existsSync(metadataPath)) { return null; } const entryPointInfo: EntryPoint = { name, - package: pkgPath, - path: entryPoint, - typings: path.resolve(entryPoint, typings), + package: packagePath, + path: entryPointPath, + typings: path.resolve(entryPointPath, typings), }; if (esm2015) { - entryPointInfo.esm2015 = path.resolve(entryPoint, esm2015); + entryPointInfo.esm2015 = path.resolve(entryPointPath, esm2015); } if (fesm2015) { - entryPointInfo.fesm2015 = path.resolve(entryPoint, fesm2015); + entryPointInfo.fesm2015 = path.resolve(entryPointPath, fesm2015); } if (fesm5) { - entryPointInfo.fesm5 = path.resolve(entryPoint, fesm5); + entryPointInfo.fesm5 = path.resolve(entryPointPath, fesm5); } if (esm5) { - entryPointInfo.esm5 = path.resolve(entryPoint, esm5); + entryPointInfo.esm5 = path.resolve(entryPointPath, esm5); } if (main) { - entryPointInfo.umd = path.resolve(entryPoint, main); + entryPointInfo.umd = path.resolve(entryPointPath, main); } return entryPointInfo; diff --git a/packages/compiler-cli/src/ngcc/src/packages/entry_point_bundle.ts b/packages/compiler-cli/src/ngcc/src/packages/entry_point_bundle.ts new file mode 100644 index 000000000000..3dbbaee26242 --- /dev/null +++ b/packages/compiler-cli/src/ngcc/src/packages/entry_point_bundle.ts @@ -0,0 +1,58 @@ +/** + * @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 * as ts from 'typescript'; + +import {BundleProgram, makeBundleProgram} from './bundle_program'; +import {EntryPoint, EntryPointFormat} from './entry_point'; + + +/** + * A bundle of files and paths (and TS programs) that correspond to a particular + * format of a package entry-point. + */ +export interface EntryPointBundle { + format: EntryPointFormat; + isFlat: boolean; + rootDirs: string[]; + src: BundleProgram; + dts: BundleProgram|null; +} + +/** + * Get an object that describes a formatted bundle for an entry-point. + * @param entryPoint The entry-point that contains the bundle. + * @param format The format of the bundle. + * @param transformDts True if processing this bundle should also process its `.d.ts` files. + */ +export function makeEntryPointBundle( + entryPoint: EntryPoint, isCore: boolean, format: EntryPointFormat, + transformDts: boolean): EntryPointBundle|null { + // Bail out if the entry-point does not have this format. + const path = entryPoint[format]; + if (!path) { + return null; + } + + // Create the TS program and necessary helpers. + const options: ts.CompilerOptions = { + allowJs: true, + maxNodeModuleJsDepth: Infinity, + rootDir: entryPoint.path, + }; + const host = ts.createCompilerHost(options); + const rootDirs = [entryPoint.path]; + + // Create the bundle programs, as necessary. + const src = makeBundleProgram(isCore, path, 'r3_symbols.js', options, host); + const dts = transformDts ? + makeBundleProgram(isCore, entryPoint.typings, 'r3_symbols.d.ts', options, host) : + null; + const isFlat = src.r3SymbolsFile === null; + + return {format, rootDirs, isFlat, src, dts}; +} diff --git a/packages/compiler-cli/src/ngcc/src/packages/transformer.ts b/packages/compiler-cli/src/ngcc/src/packages/transformer.ts index 0b13e8a83201..ce9e389290e8 100644 --- a/packages/compiler-cli/src/ngcc/src/packages/transformer.ts +++ b/packages/compiler-cli/src/ngcc/src/packages/transformer.ts @@ -5,12 +5,13 @@ * 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 {dirname, resolve} from 'canonical-path'; -import {existsSync, lstatSync, readdirSync, writeFileSync} from 'fs'; +import {dirname} from 'canonical-path'; +import {existsSync, writeFileSync} from 'fs'; import {mkdir, mv} from 'shelljs'; -import * as ts from 'typescript'; import {DecorationAnalyzer} from '../analysis/decoration_analyzer'; +import {NgccReferencesRegistry} from '../analysis/ngcc_references_registry'; +import {PrivateDeclarationsAnalyzer} from '../analysis/private_declarations_analyzer'; import {SwitchMarkerAnalyzer} from '../analysis/switch_marker_analyzer'; import {Esm2015ReflectionHost} from '../host/esm2015_host'; import {Esm5ReflectionHost} from '../host/esm5_host'; @@ -20,8 +21,8 @@ import {EsmRenderer} from '../rendering/esm_renderer'; import {FileInfo, Renderer} from '../rendering/renderer'; import {checkMarkerFile, writeMarkerFile} from './build_marker'; -import {BundleInfo, createBundleInfo} from './bundle'; -import {EntryPoint, EntryPointFormat} from './entry_point'; +import {EntryPoint} from './entry_point'; +import {EntryPointBundle} from './entry_point_bundle'; /** * A Package is stored in a directory on disk and that directory can contain one or more package @@ -31,9 +32,11 @@ import {EntryPoint, EntryPointFormat} from './entry_point'; * parsed to identify the decorated exported classes that need to be analyzed and compiled by one or * more `DecoratorHandler` objects. * - * Each entry point to a package is identified by a `SourceFile` that can be parsed and analyzed to + * Each entry point to a package is identified by a `package.json` which contains properties that + * indicate what formatted bundles are accessible via this end-point. + * + * Each bundle is identified by a root `SourceFile` that can be parsed and analyzed to * identify classes that need to be transformed; and then finally rendered and written to disk. - * The actual file which needs to be transformed depends upon the package format. * * Along with the source files, the corresponding source maps (either inline or external) and * `.d.ts` files are transformed accordingly. @@ -45,124 +48,76 @@ import {EntryPoint, EntryPointFormat} from './entry_point'; export class Transformer { constructor(private sourcePath: string, private targetPath: string) {} - transform(entryPoint: EntryPoint, format: EntryPointFormat, transformDts: boolean): void { - if (checkMarkerFile(entryPoint, format)) { - console.warn(`Skipping ${entryPoint.name} : ${format} (already built).`); + /** + * Transform the source (and typings) files of a bundle. + * @param bundle the bundle to transform. + */ + transform(entryPoint: EntryPoint, isCore: boolean, bundle: EntryPointBundle): void { + if (checkMarkerFile(entryPoint, bundle.format)) { + console.warn(`Skipping ${entryPoint.name} : ${bundle.format} (already built).`); return; } - const entryPointFilePath = entryPoint[format]; - if (!entryPointFilePath) { - console.warn( - `Skipping ${entryPoint.name} : ${format} (no entry point file for this format).`); - return; - } - - console.warn(`Compiling ${entryPoint.name} - ${format}`); - - const options: ts.CompilerOptions = { - allowJs: true, - maxNodeModuleJsDepth: Infinity, - rootDir: entryPoint.path, - }; + console.warn(`Compiling ${entryPoint.name} - ${bundle.format}`); - // Create the TS program and necessary helpers. - // TODO : create a custom compiler host that reads from .bak files if available. - const host = ts.createCompilerHost(options); - const rootDirs = this.getRootDirs(host, options); - const isCore = entryPoint.name === '@angular/core'; - const r3SymbolsPath = - isCore ? this.findR3SymbolsPath(dirname(entryPointFilePath), 'r3_symbols.js') : null; - const rootPaths = r3SymbolsPath ? [entryPointFilePath, r3SymbolsPath] : [entryPointFilePath]; - const packageProgram = ts.createProgram(rootPaths, options, host); - const r3SymbolsFile = r3SymbolsPath && packageProgram.getSourceFile(r3SymbolsPath) || null; - - // Create the program for processing DTS files if enabled for this format. - const dtsFilePath = entryPoint.typings; - let dtsProgram: ts.Program|null = null; - let r3SymbolsDtsFile: ts.SourceFile|null = null; - if (transformDts) { - console.time(`${entryPoint.name} (dtsMapper creation)`); - const r3SymbolsDtsPath = - isCore ? this.findR3SymbolsPath(dirname(dtsFilePath), 'r3_symbols.d.ts') : null; - const rootDtsPaths = r3SymbolsDtsPath ? [dtsFilePath, r3SymbolsDtsPath] : [dtsFilePath]; - - dtsProgram = ts.createProgram(rootDtsPaths, options, host); - r3SymbolsDtsFile = r3SymbolsDtsPath && dtsProgram.getSourceFile(r3SymbolsDtsPath) || null; - console.timeEnd(`${entryPoint.name} (dtsMapper creation)`); - } - - const bundle = createBundleInfo(isCore, r3SymbolsFile, r3SymbolsDtsFile); - const reflectionHost = this.getHost(isCore, format, packageProgram, dtsFilePath, dtsProgram); + const reflectionHost = this.getHost(isCore, bundle); // Parse and analyze the files. - const {decorationAnalyses, switchMarkerAnalyses} = - this.analyzeProgram(packageProgram, reflectionHost, rootDirs, isCore); + const {decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses} = + this.analyzeProgram(reflectionHost, isCore, bundle); - console.time(`${entryPoint.name} (rendering)`); // Transform the source files and source maps. - const renderer = this.getRenderer(format, packageProgram, reflectionHost, bundle, transformDts); - const renderedFiles = - renderer.renderProgram(packageProgram, decorationAnalyses, switchMarkerAnalyses); - console.timeEnd(`${entryPoint.name} (rendering)`); + const renderer = this.getRenderer(reflectionHost, isCore, bundle); + const renderedFiles = renderer.renderProgram( + decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses); // Write out all the transformed files. renderedFiles.forEach(file => this.writeFile(file)); // Write the built-with-ngcc marker - writeMarkerFile(entryPoint, format); + writeMarkerFile(entryPoint, bundle.format); } - getRootDirs(host: ts.CompilerHost, options: ts.CompilerOptions) { - if (options.rootDirs !== undefined) { - return options.rootDirs; - } else if (options.rootDir !== undefined) { - return [options.rootDir]; - } else { - return [host.getCurrentDirectory()]; - } - } - - getHost( - isCore: boolean, format: string, program: ts.Program, dtsFilePath: string, - dtsProgram: ts.Program|null): NgccReflectionHost { - switch (format) { + getHost(isCore: boolean, bundle: EntryPointBundle): NgccReflectionHost { + const typeChecker = bundle.src.program.getTypeChecker(); + switch (bundle.format) { case 'esm2015': case 'fesm2015': - return new Esm2015ReflectionHost(isCore, program.getTypeChecker(), dtsFilePath, dtsProgram); + return new Esm2015ReflectionHost(isCore, typeChecker, bundle.dts); case 'esm5': case 'fesm5': - return new Esm5ReflectionHost(isCore, program.getTypeChecker()); + return new Esm5ReflectionHost(isCore, typeChecker); default: - throw new Error(`Relection host for "${format}" not yet implemented.`); + throw new Error(`Reflection host for "${bundle.format}" not yet implemented.`); } } - getRenderer( - format: string, program: ts.Program, host: NgccReflectionHost, bundle: BundleInfo, - transformDts: boolean): Renderer { - switch (format) { + getRenderer(host: NgccReflectionHost, isCore: boolean, bundle: EntryPointBundle): Renderer { + switch (bundle.format) { case 'esm2015': case 'fesm2015': - return new EsmRenderer(host, bundle, this.sourcePath, this.targetPath, transformDts); + return new EsmRenderer(host, isCore, bundle, this.sourcePath, this.targetPath); case 'esm5': case 'fesm5': - return new Esm5Renderer(host, bundle, this.sourcePath, this.targetPath, transformDts); + return new Esm5Renderer(host, isCore, bundle, this.sourcePath, this.targetPath); default: - throw new Error(`Renderer for "${format}" not yet implemented.`); + throw new Error(`Renderer for "${bundle.format}" not yet implemented.`); } } - analyzeProgram( - program: ts.Program, reflectionHost: NgccReflectionHost, rootDirs: string[], - isCore: boolean) { - const decorationAnalyzer = - new DecorationAnalyzer(program.getTypeChecker(), reflectionHost, rootDirs, isCore); + analyzeProgram(reflectionHost: NgccReflectionHost, isCore: boolean, bundle: EntryPointBundle) { + const typeChecker = bundle.src.program.getTypeChecker(); + const referencesRegistry = new NgccReferencesRegistry(reflectionHost); + const decorationAnalyzer = new DecorationAnalyzer( + typeChecker, reflectionHost, referencesRegistry, bundle.rootDirs, isCore); const switchMarkerAnalyzer = new SwitchMarkerAnalyzer(reflectionHost); - return { - decorationAnalyses: decorationAnalyzer.analyzeProgram(program), - switchMarkerAnalyses: switchMarkerAnalyzer.analyzeProgram(program), - }; + const privateDeclarationsAnalyzer = + new PrivateDeclarationsAnalyzer(reflectionHost, referencesRegistry); + const decorationAnalyses = decorationAnalyzer.analyzeProgram(bundle.src.program); + const switchMarkerAnalyses = switchMarkerAnalyzer.analyzeProgram(bundle.src.program); + const privateDeclarationsAnalyses = + privateDeclarationsAnalyzer.analyzeProgram(bundle.src.program); + return {decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses}; } writeFile(file: FileInfo): void { @@ -173,32 +128,4 @@ export class Transformer { } writeFileSync(file.path, file.contents, 'utf8'); } - - findR3SymbolsPath(directory: string, fileName: string): string|null { - const r3SymbolsFilePath = resolve(directory, fileName); - if (existsSync(r3SymbolsFilePath)) { - return r3SymbolsFilePath; - } - - const subDirectories = - readdirSync(directory) - // Not interested in hidden files - .filter(p => !p.startsWith('.')) - // Ignore node_modules - .filter(p => p !== 'node_modules') - // Only interested in directories (and only those that are not symlinks) - .filter(p => { - const stat = lstatSync(resolve(directory, p)); - return stat.isDirectory() && !stat.isSymbolicLink(); - }); - - for (const subDirectory of subDirectories) { - const r3SymbolsFilePath = this.findR3SymbolsPath(resolve(directory, subDirectory), fileName); - if (r3SymbolsFilePath) { - return r3SymbolsFilePath; - } - } - - return null; - } } diff --git a/packages/compiler-cli/src/ngcc/src/rendering/esm5_renderer.ts b/packages/compiler-cli/src/ngcc/src/rendering/esm5_renderer.ts index 0cdf9b2bea8c..c412b0a71bda 100644 --- a/packages/compiler-cli/src/ngcc/src/rendering/esm5_renderer.ts +++ b/packages/compiler-cli/src/ngcc/src/rendering/esm5_renderer.ts @@ -9,14 +9,14 @@ import * as ts from 'typescript'; import MagicString from 'magic-string'; import {NgccReflectionHost} from '../host/ngcc_host'; import {CompiledClass} from '../analysis/decoration_analyzer'; -import {BundleInfo} from '../packages/bundle'; import {EsmRenderer} from './esm_renderer'; +import {EntryPointBundle} from '../packages/entry_point_bundle'; export class Esm5Renderer extends EsmRenderer { constructor( - protected host: NgccReflectionHost, protected bundle: BundleInfo, - protected sourcePath: string, protected targetPath: string, transformDts: boolean) { - super(host, bundle, sourcePath, targetPath, transformDts); + host: NgccReflectionHost, isCore: boolean, bundle: EntryPointBundle, sourcePath: string, + targetPath: string) { + super(host, isCore, bundle, sourcePath, targetPath); } /** diff --git a/packages/compiler-cli/src/ngcc/src/rendering/esm_renderer.ts b/packages/compiler-cli/src/ngcc/src/rendering/esm_renderer.ts index 657665da212f..578641c36a7a 100644 --- a/packages/compiler-cli/src/ngcc/src/rendering/esm_renderer.ts +++ b/packages/compiler-cli/src/ngcc/src/rendering/esm_renderer.ts @@ -5,18 +5,20 @@ * 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 * as ts from 'typescript'; +import {dirname, relative} from 'canonical-path'; import MagicString from 'magic-string'; +import * as ts from 'typescript'; import {NgccReflectionHost, POST_R3_MARKER, PRE_R3_MARKER, SwitchableVariableDeclaration} from '../host/ngcc_host'; import {CompiledClass} from '../analysis/decoration_analyzer'; -import {BundleInfo} from '../packages/bundle'; -import {Renderer} from './renderer'; +import {ExportInfo} from '../analysis/private_declarations_analyzer'; +import {Renderer, stripExtension} from './renderer'; +import {EntryPointBundle} from '../packages/entry_point_bundle'; export class EsmRenderer extends Renderer { constructor( - protected host: NgccReflectionHost, protected bundle: BundleInfo, - protected sourcePath: string, protected targetPath: string, transformDts: boolean) { - super(host, bundle, sourcePath, targetPath, transformDts); + host: NgccReflectionHost, isCore: boolean, bundle: EntryPointBundle, sourcePath: string, + targetPath: string) { + super(host, isCore, bundle, sourcePath, targetPath); } /** @@ -27,6 +29,19 @@ export class EsmRenderer extends Renderer { imports.forEach(i => { output.appendLeft(0, `import * as ${i.as} from '${i.name}';\n`); }); } + addExports(output: MagicString, entryPointBasePath: string, exports: { + identifier: string, + from: string + }[]): void { + exports.forEach(e => { + const basePath = stripExtension(e.from); + const relativePath = './' + relative(dirname(entryPointBasePath), basePath); + const exportFrom = entryPointBasePath !== basePath ? ` from '${relativePath}'` : ''; + const exportStr = `\nexport {${e.identifier}}${exportFrom};`; + output.append(exportStr); + }); + } + addConstants(output: MagicString, constants: string, file: ts.SourceFile): void { if (constants === '') { return; diff --git a/packages/compiler-cli/src/ngcc/src/rendering/renderer.ts b/packages/compiler-cli/src/ngcc/src/rendering/renderer.ts index 7e8c7ee85f7d..71e851d44ff2 100644 --- a/packages/compiler-cli/src/ngcc/src/rendering/renderer.ts +++ b/packages/compiler-cli/src/ngcc/src/rendering/renderer.ts @@ -18,10 +18,11 @@ import {CompileResult} from '@angular/compiler-cli/src/ngtsc/transform'; import {translateStatement, translateType} from '../../../ngtsc/translator'; import {NgccImportManager} from './ngcc_import_manager'; import {CompiledClass, CompiledFile, DecorationAnalyses} from '../analysis/decoration_analyzer'; +import {PrivateDeclarationsAnalyses, ExportInfo} from '../analysis/private_declarations_analyzer'; import {SwitchMarkerAnalyses, SwitchMarkerAnalysis} from '../analysis/switch_marker_analyzer'; -import {BundleInfo} from '../packages/bundle'; import {IMPORT_PREFIX} from '../constants'; import {NgccReflectionHost, SwitchableVariableDeclaration} from '../host/ngcc_host'; +import {EntryPointBundle} from '../packages/entry_point_bundle'; interface SourceMapInfo { source: string; @@ -56,29 +57,38 @@ interface DtsClassInfo { */ export abstract class Renderer { constructor( - protected host: NgccReflectionHost, protected bundle: BundleInfo, - protected sourcePath: string, protected targetPath: string, protected transformDts: boolean) { - } + protected host: NgccReflectionHost, protected isCore: boolean, + protected bundle: EntryPointBundle, protected sourcePath: string, + protected targetPath: string) {} renderProgram( - program: ts.Program, decorationAnalyses: DecorationAnalyses, - switchMarkerAnalyses: SwitchMarkerAnalyses): FileInfo[] { + decorationAnalyses: DecorationAnalyses, switchMarkerAnalyses: SwitchMarkerAnalyses, + privateDeclarationsAnalyses: PrivateDeclarationsAnalyses): FileInfo[] { const renderedFiles: FileInfo[] = []; // Transform the source files. - program.getSourceFiles().map(sourceFile => { + this.bundle.src.program.getSourceFiles().map(sourceFile => { const compiledFile = decorationAnalyses.get(sourceFile); const switchMarkerAnalysis = switchMarkerAnalyses.get(sourceFile); - if (compiledFile || switchMarkerAnalysis) { - renderedFiles.push(...this.renderFile(sourceFile, compiledFile, switchMarkerAnalysis)); + if (compiledFile || switchMarkerAnalysis || sourceFile === this.bundle.src.file) { + renderedFiles.push(...this.renderFile( + sourceFile, compiledFile, switchMarkerAnalysis, privateDeclarationsAnalyses)); } }); - if (this.transformDts) { - // Transform the .d.ts files + // Transform the .d.ts files + if (this.bundle.dts) { const dtsFiles = this.getTypingsFilesToRender(decorationAnalyses); - dtsFiles.forEach((classes, file) => renderedFiles.push(...this.renderDtsFile(file, classes))); + + // If the dts entry-point is not already there (it did not have compiled classes) + // then add it now, to ensure it gets its extra exports rendered. + if (!dtsFiles.has(this.bundle.dts.file)) { + dtsFiles.set(this.bundle.dts.file, []); + } + dtsFiles.forEach( + (classes, file) => renderedFiles.push( + ...this.renderDtsFile(file, classes, privateDeclarationsAnalyses))); } return renderedFiles; @@ -91,7 +101,8 @@ export abstract class Renderer { */ renderFile( sourceFile: ts.SourceFile, compiledFile: CompiledFile|undefined, - switchMarkerAnalysis: SwitchMarkerAnalysis|undefined): FileInfo[] { + switchMarkerAnalysis: SwitchMarkerAnalysis|undefined, + privateDeclarationsAnalyses: PrivateDeclarationsAnalyses): FileInfo[] { const input = this.extractSourceMap(sourceFile); const outputText = new MagicString(input.source); @@ -101,8 +112,7 @@ export abstract class Renderer { } if (compiledFile) { - const importManager = - new NgccImportManager(this.bundle.isFlat, this.bundle.isCore, IMPORT_PREFIX); + const importManager = new NgccImportManager(this.bundle.isFlat, this.isCore, IMPORT_PREFIX); const decoratorsToRemove = new Map(); compiledFile.compiledClasses.forEach(clazz => { @@ -118,20 +128,28 @@ export abstract class Renderer { this.addImports( outputText, importManager.getAllImports( - compiledFile.sourceFile.fileName, this.bundle.rewriteCoreImportsTo)); + compiledFile.sourceFile.fileName, this.bundle.src.r3SymbolsFile)); - // TODO: remove contructor param metadata and property decorators (we need info from the + // TODO: remove constructor param metadata and property decorators (we need info from the // handlers to do this) this.removeDecorators(outputText, decoratorsToRemove); } + // Add exports to the entry-point file + if (sourceFile === this.bundle.src.file) { + const entryPointBasePath = stripExtension(this.bundle.src.path); + this.addExports(outputText, entryPointBasePath, privateDeclarationsAnalyses); + } + return this.renderSourceAndMap(sourceFile, input, outputText); } - renderDtsFile(dtsFile: ts.SourceFile, dtsClasses: DtsClassInfo[]): FileInfo[] { + renderDtsFile( + dtsFile: ts.SourceFile, dtsClasses: DtsClassInfo[], + privateDeclarationsAnalyses: PrivateDeclarationsAnalyses): FileInfo[] { const input = this.extractSourceMap(dtsFile); const outputText = new MagicString(input.source); - const importManager = new NgccImportManager(false, this.bundle.isCore, IMPORT_PREFIX); + const importManager = new NgccImportManager(false, this.isCore, IMPORT_PREFIX); dtsClasses.forEach(dtsClass => { const endOfClass = dtsClass.dtsDeclaration.getEnd(); @@ -143,8 +161,21 @@ export abstract class Renderer { }); this.addImports( - outputText, - importManager.getAllImports(dtsFile.fileName, this.bundle.rewriteCoreDtsImportsTo)); + outputText, importManager.getAllImports(dtsFile.fileName, this.bundle.dts !.r3SymbolsFile)); + + if (dtsFile === this.bundle.dts !.file) { + const dtsExports = privateDeclarationsAnalyses.map(e => { + if (!e.dtsFrom) { + throw new Error( + `There is no typings path for ${e.identifier} in ${e.from}.\n` + + `We need to add an export for this class to a .d.ts typings file because ` + + `Angular compiler needs to be able to reference this class in compiled code, such as templates.\n` + + `The simplest fix for this is to ensure that this class is exported from the package's entry-point.`); + } + return {identifier: e.identifier, from: e.dtsFrom}; + }); + this.addExports(outputText, dtsFile.fileName, dtsExports); + } return this.renderSourceAndMap(dtsFile, input, outputText); } @@ -152,6 +183,10 @@ export abstract class Renderer { protected abstract addConstants(output: MagicString, constants: string, file: ts.SourceFile): void; protected abstract addImports(output: MagicString, imports: {name: string, as: string}[]): void; + protected abstract addExports(output: MagicString, entryPointBasePath: string, exports: { + identifier: string, + from: string + }[]): void; protected abstract addDefinitions( output: MagicString, compiledClass: CompiledClass, definitions: string): void; protected abstract removeDecorators( @@ -337,6 +372,10 @@ export function renderDefinitions( return definitions; } +export function stripExtension(filePath: string): string { + return filePath.replace(/\.(js|d\.ts$)/, ''); +} + /** * Create an Angular AST statement node that contains the assignment of the * compiled decorator to be applied to the class. diff --git a/packages/compiler-cli/src/ngcc/src/utils.ts b/packages/compiler-cli/src/ngcc/src/utils.ts index 94682bd8b6c4..ceef984d94c0 100644 --- a/packages/compiler-cli/src/ngcc/src/utils.ts +++ b/packages/compiler-cli/src/ngcc/src/utils.ts @@ -40,3 +40,13 @@ export function findAll(node: ts.Node, test: (node: ts.Node) => node is ts.No } } } + +/** + * Does the given declaration have a name which is an identifier? + * @param declaration The declaration to test. + * @returns true if the declaration has an identifer for a name. + */ +export function hasNameIdentifier(declaration: ts.Declaration): declaration is ts.Declaration& + {name: ts.Identifier} { + return ts.isIdentifier((declaration as any).name); +} diff --git a/packages/compiler-cli/src/ngcc/test/analysis/decoration_analyzer_spec.ts b/packages/compiler-cli/src/ngcc/test/analysis/decoration_analyzer_spec.ts index 2d435ca63891..1978ba02c82f 100644 --- a/packages/compiler-cli/src/ngcc/test/analysis/decoration_analyzer_spec.ts +++ b/packages/compiler-cli/src/ngcc/test/analysis/decoration_analyzer_spec.ts @@ -10,9 +10,10 @@ import * as ts from 'typescript'; import {Decorator} from '../../../ngtsc/host'; import {DecoratorHandler} from '../../../ngtsc/transform'; import {DecorationAnalyses, DecorationAnalyzer} from '../../src/analysis/decoration_analyzer'; +import {NgccReferencesRegistry} from '../../src/analysis/ngcc_references_registry'; import {Esm2015ReflectionHost} from '../../src/host/esm2015_host'; -import {makeProgram} from '../helpers/utils'; +import {makeTestProgram} from '../helpers/utils'; const TEST_PROGRAM = { name: 'test.js', @@ -83,10 +84,11 @@ describe('DecorationAnalyzer', () => { let result: DecorationAnalyses; beforeEach(() => { - program = makeProgram(TEST_PROGRAM); - const analyzer = new DecorationAnalyzer( - program.getTypeChecker(), new Esm2015ReflectionHost(false, program.getTypeChecker()), - [''], false); + program = makeTestProgram(TEST_PROGRAM); + const host = new Esm2015ReflectionHost(false, program.getTypeChecker()); + const referencesRegistry = new NgccReferencesRegistry(host); + const analyzer = + new DecorationAnalyzer(program.getTypeChecker(), host, referencesRegistry, [''], false); testHandler = createTestHandler(); analyzer.handlers = [testHandler]; result = analyzer.analyzeProgram(program); @@ -125,10 +127,11 @@ describe('DecorationAnalyzer', () => { // is not yet solved. it('should analyze an internally imported component, which is not publicly exported from the entry-point', () => { - const program = makeProgram(...INTERNAL_COMPONENT_PROGRAM); + const program = makeTestProgram(...INTERNAL_COMPONENT_PROGRAM); + const host = new Esm2015ReflectionHost(false, program.getTypeChecker()); + const referencesRegistry = new NgccReferencesRegistry(host); const analyzer = new DecorationAnalyzer( - program.getTypeChecker(), new Esm2015ReflectionHost(false, program.getTypeChecker()), - [''], false); + program.getTypeChecker(), host, referencesRegistry, [''], false); const testHandler = createTestHandler(); analyzer.handlers = [testHandler]; const result = analyzer.analyzeProgram(program); @@ -141,10 +144,11 @@ describe('DecorationAnalyzer', () => { }); it('should analyze an internally defined component, which is not exported at all', () => { - const program = makeProgram(...INTERNAL_COMPONENT_PROGRAM); - const analyzer = new DecorationAnalyzer( - program.getTypeChecker(), new Esm2015ReflectionHost(false, program.getTypeChecker()), - [''], false); + const program = makeTestProgram(...INTERNAL_COMPONENT_PROGRAM); + const host = new Esm2015ReflectionHost(false, program.getTypeChecker()); + const referencesRegistry = new NgccReferencesRegistry(host); + const analyzer = + new DecorationAnalyzer(program.getTypeChecker(), host, referencesRegistry, [''], false); const testHandler = createTestHandler(); analyzer.handlers = [testHandler]; const result = analyzer.analyzeProgram(program); diff --git a/packages/compiler-cli/src/ngcc/test/analysis/private_declarations_analyzer_spec.ts b/packages/compiler-cli/src/ngcc/test/analysis/private_declarations_analyzer_spec.ts new file mode 100644 index 000000000000..9188a5344b61 --- /dev/null +++ b/packages/compiler-cli/src/ngcc/test/analysis/private_declarations_analyzer_spec.ts @@ -0,0 +1,158 @@ +/** + * @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 {ResolvedReference} from '@angular/compiler-cli/src/ngtsc/metadata'; +import * as ts from 'typescript'; + +import {NgccReferencesRegistry} from '../../src/analysis/ngcc_references_registry'; +import {PrivateDeclarationsAnalyzer} from '../../src/analysis/private_declarations_analyzer'; +import {Esm2015ReflectionHost} from '../../src/host/esm2015_host'; +import {getDeclaration, makeTestBundleProgram, makeTestProgram} from '../helpers/utils'; + +const TEST_PROGRAM = [ + { + name: '/src/entry_point.js', + isRoot: true, + contents: ` + export {PublicComponent} from './a'; + export {ModuleA} from './mod'; + export {ModuleB} from './b'; + ` + }, + { + name: '/src/a.js', + isRoot: false, + contents: ` + import {Component} from '@angular/core'; + export class PublicComponent {} + PublicComponent.decorators = [ + {type: Component, args: [{selectors: 'a', template: ''}]} + ]; + ` + }, + { + name: '/src/b.js', + isRoot: false, + contents: ` + import {Component, NgModule} from '@angular/core'; + class PrivateComponent {} + PrivateComponent.decorators = [ + {type: Component, args: [{selectors: 'b', template: ''}]} + ]; + export class ModuleB {} + ModuleB.decorators = [ + {type: NgModule, args: [{declarations: [PrivateComponent]}]} + ]; + ` + }, + { + name: '/src/c.js', + isRoot: false, + contents: ` + import {Component} from '@angular/core'; + export class InternalComponent {} + InternalComponent.decorators = [ + {type: Component, args: [{selectors: 'c', template: ''}]} + ]; + ` + }, + { + name: '/src/mod.js', + isRoot: false, + contents: ` + import {Component, NgModule} from '@angular/core'; + import {PublicComponent} from './a'; + import {ModuleB} from './b'; + import {InternalComponent} from './c'; + export class ModuleA {} + ModuleA.decorators = [ + {type: NgModule, args: [{ + declarations: [PublicComponent, InternalComponent], + imports: [ModuleB] + }]} + ]; + ` + } +]; +const TEST_DTS_PROGRAM = [ + { + name: '/typings/entry_point.d.ts', + isRoot: true, + contents: ` + export {PublicComponent} from './a'; + export {ModuleA} from './mod'; + export {ModuleB} from './b'; + ` + }, + { + name: '/typings/a.d.ts', + isRoot: false, + contents: ` + export declare class PublicComponent {} + ` + }, + { + name: '/typings/b.d.ts', + isRoot: false, + contents: ` + export declare class ModuleB {} + ` + }, + { + name: '/typings/c.d.ts', + isRoot: false, + contents: ` + export declare class InternalComponent {} + ` + }, + { + name: '/typings/mod.d.ts', + isRoot: false, + contents: ` + import {PublicComponent} from './a'; + import {ModuleB} from './b'; + import {InternalComponent} from './c'; + export declare class ModuleA {} + ` + }, +]; + +describe('PrivateDeclarationsAnalyzer', () => { + describe('analyzeProgram()', () => { + it('should find all NgModule declarations that were not publicly exported from the entry-point', + () => { + const program = makeTestProgram(...TEST_PROGRAM); + const dts = makeTestBundleProgram(TEST_DTS_PROGRAM); + const host = new Esm2015ReflectionHost(false, program.getTypeChecker(), dts); + const referencesRegistry = new NgccReferencesRegistry(host); + const analyzer = new PrivateDeclarationsAnalyzer(host, referencesRegistry); + + // Set up the registry with references - this would normally be done by the + // decoration handlers in the `DecorationAnalyzer`. + const publicComponentDeclaration = + getDeclaration(program, '/src/a.js', 'PublicComponent', ts.isClassDeclaration); + referencesRegistry.add( + new ResolvedReference(publicComponentDeclaration, publicComponentDeclaration.name !)); + const privateComponentDeclaration = + getDeclaration(program, '/src/b.js', 'PrivateComponent', ts.isClassDeclaration); + referencesRegistry.add(new ResolvedReference( + privateComponentDeclaration, privateComponentDeclaration.name !)); + const internalComponentDeclaration = + getDeclaration(program, '/src/c.js', 'InternalComponent', ts.isClassDeclaration); + referencesRegistry.add(new ResolvedReference( + internalComponentDeclaration, internalComponentDeclaration.name !)); + + const analyses = analyzer.analyzeProgram(program); + expect(analyses.length).toEqual(2); + expect(analyses).toEqual([ + {identifier: 'PrivateComponent', from: '/src/b.js', dtsFrom: null}, + {identifier: 'InternalComponent', from: '/src/c.js', dtsFrom: '/typings/c.d.ts'}, + ]); + }); + }); +}); diff --git a/packages/compiler-cli/src/ngcc/test/analysis/references_registry_spec.ts b/packages/compiler-cli/src/ngcc/test/analysis/references_registry_spec.ts new file mode 100644 index 000000000000..b2f9ce27aaea --- /dev/null +++ b/packages/compiler-cli/src/ngcc/test/analysis/references_registry_spec.ts @@ -0,0 +1,52 @@ +/** + * @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 * as ts from 'typescript'; + +import {Reference, TypeScriptReflectionHost, staticallyResolve} from '../../../ngtsc/metadata'; +import {getDeclaration, makeProgram} from '../../../ngtsc/testing/in_memory_typescript'; +import {NgccReferencesRegistry} from '../../src/analysis/ngcc_references_registry'; + +describe('NgccReferencesRegistry', () => { + it('should return a mapping from resolved reference identifiers to their declarations', () => { + const {program} = makeProgram([{ + name: 'index.ts', + contents: ` + export class SomeClass {} + export function someFunction() {} + export const someVariable = 42; + + export const testArray = [SomeClass, someFunction, someVariable]; + ` + }]); + + const checker = program.getTypeChecker(); + + const testArrayDeclaration = + getDeclaration(program, 'index.ts', 'testArray', ts.isVariableDeclaration); + const someClassDecl = getDeclaration(program, 'index.ts', 'SomeClass', ts.isClassDeclaration); + const someFunctionDecl = + getDeclaration(program, 'index.ts', 'someFunction', ts.isFunctionDeclaration); + const someVariableDecl = + getDeclaration(program, 'index.ts', 'someVariable', ts.isVariableDeclaration); + const testArrayExpression = testArrayDeclaration.initializer !; + + const host = new TypeScriptReflectionHost(checker); + const registry = new NgccReferencesRegistry(host); + + const references = + staticallyResolve(testArrayExpression, host, checker) as Reference[]; + registry.add(...references); + + const map = registry.getDeclarationMap(); + expect(map.size).toEqual(2); + expect(map.get(someClassDecl.name !) !.node).toBe(someClassDecl); + expect(map.get(someFunctionDecl.name !) !.node).toBe(someFunctionDecl); + expect(map.has(someVariableDecl.name as ts.Identifier)).toBe(false); + }); +}); diff --git a/packages/compiler-cli/src/ngcc/test/analysis/switch_marker_analyzer_spec.ts b/packages/compiler-cli/src/ngcc/test/analysis/switch_marker_analyzer_spec.ts index b97daa07eeb6..ff27fd9d1ca0 100644 --- a/packages/compiler-cli/src/ngcc/test/analysis/switch_marker_analyzer_spec.ts +++ b/packages/compiler-cli/src/ngcc/test/analysis/switch_marker_analyzer_spec.ts @@ -9,7 +9,7 @@ import * as ts from 'typescript'; import {SwitchMarkerAnalyzer} from '../../src/analysis/switch_marker_analyzer'; import {Esm2015ReflectionHost} from '../../src/host/esm2015_host'; -import {makeProgram} from '../helpers/utils'; +import {makeTestProgram} from '../helpers/utils'; const TEST_PROGRAM = [ { @@ -46,7 +46,7 @@ const TEST_PROGRAM = [ describe('SwitchMarkerAnalyzer', () => { describe('analyzeProgram()', () => { it('should check for switchable markers in all the files of the program', () => { - const program = makeProgram(...TEST_PROGRAM); + const program = makeTestProgram(...TEST_PROGRAM); const host = new Esm2015ReflectionHost(false, program.getTypeChecker()); const analyzer = new SwitchMarkerAnalyzer(host); const analysis = analyzer.analyzeProgram(program); diff --git a/packages/compiler-cli/src/ngcc/test/helpers/utils.ts b/packages/compiler-cli/src/ngcc/test/helpers/utils.ts index 705f2e26697d..1d466683ab2e 100644 --- a/packages/compiler-cli/src/ngcc/test/helpers/utils.ts +++ b/packages/compiler-cli/src/ngcc/test/helpers/utils.ts @@ -6,12 +6,48 @@ * found in the LICENSE file at https://angular.io/license */ import * as ts from 'typescript'; -import {makeProgram as _makeProgram} from '../../../ngtsc/testing/in_memory_typescript'; + +import {makeProgram} from '../../../ngtsc/testing/in_memory_typescript'; +import {BundleProgram} from '../../src/packages/bundle_program'; +import {EntryPointFormat} from '../../src/packages/entry_point'; +import {EntryPointBundle} from '../../src/packages/entry_point_bundle'; export {getDeclaration} from '../../../ngtsc/testing/in_memory_typescript'; -export function makeProgram(...files: {name: string, contents: string}[]): ts.Program { - return _makeProgram([getFakeCore(), getFakeTslib(), ...files], {allowJs: true, checkJs: false}) + +/** + * + * @param format The format of the bundle. + * @param files The source files to include in the bundle. + * @param dtsFiles The typings files to include the bundle. + */ +export function makeTestEntryPointBundle( + format: EntryPointFormat, files: {name: string, contents: string, isRoot?: boolean}[], + dtsFiles?: {name: string, contents: string, isRoot?: boolean}[]): EntryPointBundle { + const src = makeTestBundleProgram(files); + const dts = dtsFiles ? makeTestBundleProgram(dtsFiles) : null; + const isFlat = src.r3SymbolsFile === null; + return {format, rootDirs: ['/'], src, dts, isFlat}; +} + +/** + * Create a bundle program for testing. + * @param files The source files of the bundle program. + */ +export function makeTestBundleProgram(files: {name: string, contents: string}[]): BundleProgram { + const program = makeTestProgram(...files); + const path = files[0].name; + const file = program.getSourceFile(path) !; + const r3SymbolsInfo = files.find(file => file.name.indexOf('r3_symbols') !== -1) || null; + const r3SymbolsPath = r3SymbolsInfo && r3SymbolsInfo.name; + const r3SymbolsFile = r3SymbolsPath && program.getSourceFile(r3SymbolsPath) || null; + return {program, path, file, r3SymbolsPath, r3SymbolsFile}; +} + + +export function makeTestProgram( + ...files: {name: string, contents: string, isRoot?: boolean | undefined}[]): ts.Program { + return makeProgram([getFakeCore(), getFakeTslib(), ...files], {allowJs: true, checkJs: false}) .program; } diff --git a/packages/compiler-cli/src/ngcc/test/host/esm2015_host_import_helper_spec.ts b/packages/compiler-cli/src/ngcc/test/host/esm2015_host_import_helper_spec.ts index 51b2120b8fbd..1e38b6eb12c3 100644 --- a/packages/compiler-cli/src/ngcc/test/host/esm2015_host_import_helper_spec.ts +++ b/packages/compiler-cli/src/ngcc/test/host/esm2015_host_import_helper_spec.ts @@ -10,7 +10,7 @@ import * as ts from 'typescript'; import {ClassMemberKind, Import} from '../../../ngtsc/host'; import {Esm2015ReflectionHost} from '../../src/host/esm2015_host'; -import {convertToDirectTsLibImport, getDeclaration, makeProgram} from '../helpers/utils'; +import {convertToDirectTsLibImport, getDeclaration, makeTestProgram} from '../helpers/utils'; const FILES = [ { @@ -103,7 +103,7 @@ describe('Fesm2015ReflectionHost [import helper style]', () => { describe('getDecoratorsOfDeclaration()', () => { it('should find the decorators on a class', () => { - const program = makeProgram(fileSystem.files[0]); + const program = makeTestProgram(fileSystem.files[0]); const host = new Esm2015ReflectionHost(false, program.getTypeChecker()); const classNode = getDeclaration( program, '/some_directive.js', 'SomeDirective', ts.isVariableDeclaration); @@ -127,7 +127,7 @@ describe('Fesm2015ReflectionHost [import helper style]', () => { {from: '@angular/core', name: 'Directive'} : {}); - const program = makeProgram(fileSystem.files[0]); + const program = makeTestProgram(fileSystem.files[0]); const host = new Esm2015ReflectionHost(false, program.getTypeChecker()); const classNode = getDeclaration( program, '/some_directive.js', 'SomeDirective', ts.isVariableDeclaration); @@ -142,7 +142,7 @@ describe('Fesm2015ReflectionHost [import helper style]', () => { }); it('should support decorators being used inside @angular/core', () => { - const program = makeProgram(fileSystem.files[1]); + const program = makeTestProgram(fileSystem.files[1]); const host = new Esm2015ReflectionHost(true, program.getTypeChecker()); const classNode = getDeclaration( program, '/node_modules/@angular/core/some_directive.js', 'SomeDirective', @@ -163,7 +163,7 @@ describe('Fesm2015ReflectionHost [import helper style]', () => { describe('getMembersOfClass()', () => { it('should find decorated members on a class', () => { - const program = makeProgram(fileSystem.files[0]); + const program = makeTestProgram(fileSystem.files[0]); const host = new Esm2015ReflectionHost(false, program.getTypeChecker()); const classNode = getDeclaration( program, '/some_directive.js', 'SomeDirective', ts.isVariableDeclaration); @@ -181,7 +181,7 @@ describe('Fesm2015ReflectionHost [import helper style]', () => { }); it('should find non decorated properties on a class', () => { - const program = makeProgram(fileSystem.files[0]); + const program = makeTestProgram(fileSystem.files[0]); const host = new Esm2015ReflectionHost(false, program.getTypeChecker()); const classNode = getDeclaration( program, '/some_directive.js', 'SomeDirective', ts.isVariableDeclaration); @@ -195,7 +195,7 @@ describe('Fesm2015ReflectionHost [import helper style]', () => { }); it('should find static methods on a class', () => { - const program = makeProgram(fileSystem.files[0]); + const program = makeTestProgram(fileSystem.files[0]); const host = new Esm2015ReflectionHost(false, program.getTypeChecker()); const classNode = getDeclaration( program, '/some_directive.js', 'SomeDirective', ts.isVariableDeclaration); @@ -208,7 +208,7 @@ describe('Fesm2015ReflectionHost [import helper style]', () => { }); it('should find static properties on a class', () => { - const program = makeProgram(fileSystem.files[0]); + const program = makeTestProgram(fileSystem.files[0]); const host = new Esm2015ReflectionHost(false, program.getTypeChecker()); const classNode = getDeclaration( program, '/some_directive.js', 'SomeDirective', ts.isVariableDeclaration); @@ -225,7 +225,7 @@ describe('Fesm2015ReflectionHost [import helper style]', () => { const spy = spyOn(Esm2015ReflectionHost.prototype, 'getImportOfIdentifier').and.returnValue({}); - const program = makeProgram(fileSystem.files[0]); + const program = makeTestProgram(fileSystem.files[0]); const host = new Esm2015ReflectionHost(false, program.getTypeChecker()); const classNode = getDeclaration( program, '/some_directive.js', 'SomeDirective', ts.isVariableDeclaration); @@ -236,7 +236,7 @@ describe('Fesm2015ReflectionHost [import helper style]', () => { }); it('should support decorators being used inside @angular/core', () => { - const program = makeProgram(fileSystem.files[1]); + const program = makeTestProgram(fileSystem.files[1]); const host = new Esm2015ReflectionHost(true, program.getTypeChecker()); const classNode = getDeclaration( program, '/node_modules/@angular/core/some_directive.js', 'SomeDirective', @@ -252,7 +252,7 @@ describe('Fesm2015ReflectionHost [import helper style]', () => { describe('getConstructorParameters', () => { it('should find the decorated constructor parameters', () => { - const program = makeProgram(fileSystem.files[0]); + const program = makeTestProgram(fileSystem.files[0]); const host = new Esm2015ReflectionHost(false, program.getTypeChecker()); const classNode = getDeclaration( program, '/some_directive.js', 'SomeDirective', ts.isVariableDeclaration); @@ -273,7 +273,7 @@ describe('Fesm2015ReflectionHost [import helper style]', () => { const spy = spyOn(Esm2015ReflectionHost.prototype, 'getImportOfIdentifier') .and.returnValue(mockImportInfo); - const program = makeProgram(fileSystem.files[0]); + const program = makeTestProgram(fileSystem.files[0]); const host = new Esm2015ReflectionHost(false, program.getTypeChecker()); const classNode = getDeclaration( program, '/some_directive.js', 'SomeDirective', ts.isVariableDeclaration); @@ -291,7 +291,7 @@ describe('Fesm2015ReflectionHost [import helper style]', () => { describe('getDeclarationOfIdentifier', () => { it('should return the declaration of a locally defined identifier', () => { - const program = makeProgram(fileSystem.files[0]); + const program = makeTestProgram(fileSystem.files[0]); const host = new Esm2015ReflectionHost(false, program.getTypeChecker()); const classNode = getDeclaration( program, '/some_directive.js', 'SomeDirective', ts.isVariableDeclaration); @@ -307,7 +307,7 @@ describe('Fesm2015ReflectionHost [import helper style]', () => { }); it('should return the declaration of an externally defined identifier', () => { - const program = makeProgram(fileSystem.files[0]); + const program = makeTestProgram(fileSystem.files[0]); const host = new Esm2015ReflectionHost(false, program.getTypeChecker()); const classNode = getDeclaration( program, '/some_directive.js', 'SomeDirective', ts.isVariableDeclaration); @@ -330,7 +330,7 @@ describe('Fesm2015ReflectionHost [import helper style]', () => { describe('getVariableValue', () => { it('should find the "actual" declaration of an aliased variable identifier', () => { - const program = makeProgram(fileSystem.files[2]); + const program = makeTestProgram(fileSystem.files[2]); const host = new Esm2015ReflectionHost(false, program.getTypeChecker()); const ngModuleRef = findVariableDeclaration( program.getSourceFile(fileSystem.files[2].name) !, 'HttpClientXsrfModule_1'); @@ -345,7 +345,7 @@ describe('Fesm2015ReflectionHost [import helper style]', () => { }); it('should return null if the variable has no assignment', () => { - const program = makeProgram(fileSystem.files[2]); + const program = makeTestProgram(fileSystem.files[2]); const host = new Esm2015ReflectionHost(false, program.getTypeChecker()); const missingValue = findVariableDeclaration( program.getSourceFile(fileSystem.files[2].name) !, 'missingValue'); @@ -354,7 +354,7 @@ describe('Fesm2015ReflectionHost [import helper style]', () => { }); it('should return null if the variable is not assigned from a call to __decorate', () => { - const program = makeProgram(fileSystem.files[2]); + const program = makeTestProgram(fileSystem.files[2]); const host = new Esm2015ReflectionHost(false, program.getTypeChecker()); const nonDecoratedVar = findVariableDeclaration( program.getSourceFile(fileSystem.files[2].name) !, 'nonDecoratedVar'); diff --git a/packages/compiler-cli/src/ngcc/test/host/esm2015_host_spec.ts b/packages/compiler-cli/src/ngcc/test/host/esm2015_host_spec.ts index e9493946b04c..790d610cd9de 100644 --- a/packages/compiler-cli/src/ngcc/test/host/esm2015_host_spec.ts +++ b/packages/compiler-cli/src/ngcc/test/host/esm2015_host_spec.ts @@ -7,9 +7,10 @@ */ import * as ts from 'typescript'; + import {ClassMemberKind, Import} from '../../../ngtsc/host'; import {Esm2015ReflectionHost} from '../../src/host/esm2015_host'; -import {getDeclaration, makeProgram} from '../helpers/utils'; +import {getDeclaration, makeTestBundleProgram, makeTestProgram} from '../helpers/utils'; const SOME_DIRECTIVE_FILE = { name: '/some_directive.js', @@ -445,9 +446,14 @@ const ARITY_CLASSES = [ ]; const TYPINGS_SRC_FILES = [ - {name: '/src/index.js', contents: `export * from './class1'; export * from './class2';`}, + { + name: '/src/index.js', + contents: + `import {InternalClass} from './internal'; export * from './class1'; export * from './class2';` + }, {name: '/src/class1.js', contents: 'export class Class1 {}\nexport class MissingClass1 {}'}, {name: '/src/class2.js', contents: 'export class Class2 {}'}, + {name: '/src/internal.js', contents: 'export class InternalClass {}\nexport class Class2 {}'}, {name: '/src/missing-class.js', contents: 'export class MissingClass2 {}'}, { name: '/src/flat-file.js', contents: @@ -456,7 +462,11 @@ const TYPINGS_SRC_FILES = [ ]; const TYPINGS_DTS_FILES = [ - {name: '/typings/index.d.ts', contents: `export * from './class1'; export * from './class2';`}, + { + name: '/typings/index.d.ts', + contents: + `import {InternalClass} from './internal'; export * from './class1'; export * from './class2';` + }, { name: '/typings/class1.d.ts', contents: `export declare class Class1 {}\nexport declare class OtherClass {}` @@ -466,6 +476,10 @@ const TYPINGS_DTS_FILES = [ contents: `export declare class Class2 {}\nexport declare interface SomeInterface {}\nexport {Class3 as xClass3} from './class3';` }, + { + name: '/typings/internal.d.ts', + contents: `export declare class InternalClass {}\nexport declare class Class2 {}` + }, {name: '/typings/class3.d.ts', contents: `export declare class Class3 {}`}, ]; @@ -473,7 +487,7 @@ describe('Fesm2015ReflectionHost', () => { describe('getDecoratorsOfDeclaration()', () => { it('should find the decorators on a class', () => { - const program = makeProgram(SOME_DIRECTIVE_FILE); + const program = makeTestProgram(SOME_DIRECTIVE_FILE); const host = new Esm2015ReflectionHost(false, program.getTypeChecker()); const classNode = getDeclaration(program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', ts.isClassDeclaration); @@ -491,7 +505,7 @@ describe('Fesm2015ReflectionHost', () => { }); it('should return null if the symbol is not a class', () => { - const program = makeProgram(FOO_FUNCTION_FILE); + const program = makeTestProgram(FOO_FUNCTION_FILE); const host = new Esm2015ReflectionHost(false, program.getTypeChecker()); const functionNode = getDeclaration(program, FOO_FUNCTION_FILE.name, 'foo', ts.isFunctionDeclaration); @@ -500,7 +514,7 @@ describe('Fesm2015ReflectionHost', () => { }); it('should return null if there are no decorators', () => { - const program = makeProgram(SIMPLE_CLASS_FILE); + const program = makeTestProgram(SIMPLE_CLASS_FILE); const host = new Esm2015ReflectionHost(false, program.getTypeChecker()); const classNode = getDeclaration(program, SIMPLE_CLASS_FILE.name, 'EmptyClass', ts.isClassDeclaration); @@ -509,7 +523,7 @@ describe('Fesm2015ReflectionHost', () => { }); it('should ignore `decorators` if it is not an array literal', () => { - const program = makeProgram(INVALID_DECORATORS_FILE); + const program = makeTestProgram(INVALID_DECORATORS_FILE); const host = new Esm2015ReflectionHost(false, program.getTypeChecker()); const classNode = getDeclaration( program, INVALID_DECORATORS_FILE.name, 'NotArrayLiteral', ts.isClassDeclaration); @@ -518,7 +532,7 @@ describe('Fesm2015ReflectionHost', () => { }); it('should ignore decorator elements that are not object literals', () => { - const program = makeProgram(INVALID_DECORATORS_FILE); + const program = makeTestProgram(INVALID_DECORATORS_FILE); const host = new Esm2015ReflectionHost(false, program.getTypeChecker()); const classNode = getDeclaration( program, INVALID_DECORATORS_FILE.name, 'NotObjectLiteral', ts.isClassDeclaration); @@ -529,7 +543,7 @@ describe('Fesm2015ReflectionHost', () => { }); it('should ignore decorator elements that have no `type` property', () => { - const program = makeProgram(INVALID_DECORATORS_FILE); + const program = makeTestProgram(INVALID_DECORATORS_FILE); const host = new Esm2015ReflectionHost(false, program.getTypeChecker()); const classNode = getDeclaration( program, INVALID_DECORATORS_FILE.name, 'NoTypeProperty', ts.isClassDeclaration); @@ -540,7 +554,7 @@ describe('Fesm2015ReflectionHost', () => { }); it('should ignore decorator elements whose `type` value is not an identifier', () => { - const program = makeProgram(INVALID_DECORATORS_FILE); + const program = makeTestProgram(INVALID_DECORATORS_FILE); const host = new Esm2015ReflectionHost(false, program.getTypeChecker()); const classNode = getDeclaration( program, INVALID_DECORATORS_FILE.name, 'NotIdentifier', ts.isClassDeclaration); @@ -555,7 +569,7 @@ describe('Fesm2015ReflectionHost', () => { const spy = spyOn(Esm2015ReflectionHost.prototype, 'getImportOfIdentifier') .and.returnValue(mockImportInfo); - const program = makeProgram(SOME_DIRECTIVE_FILE); + const program = makeTestProgram(SOME_DIRECTIVE_FILE); const host = new Esm2015ReflectionHost(false, program.getTypeChecker()); const classNode = getDeclaration(program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', ts.isClassDeclaration); @@ -570,7 +584,7 @@ describe('Fesm2015ReflectionHost', () => { describe('(returned decorators `args`)', () => { it('should be an empty array if decorator has no `args` property', () => { - const program = makeProgram(INVALID_DECORATOR_ARGS_FILE); + const program = makeTestProgram(INVALID_DECORATOR_ARGS_FILE); const host = new Esm2015ReflectionHost(false, program.getTypeChecker()); const classNode = getDeclaration( program, INVALID_DECORATOR_ARGS_FILE.name, 'NoArgsProperty', ts.isClassDeclaration); @@ -582,7 +596,7 @@ describe('Fesm2015ReflectionHost', () => { }); it('should be an empty array if decorator\'s `args` has no property assignment', () => { - const program = makeProgram(INVALID_DECORATOR_ARGS_FILE); + const program = makeTestProgram(INVALID_DECORATOR_ARGS_FILE); const host = new Esm2015ReflectionHost(false, program.getTypeChecker()); const classNode = getDeclaration( program, INVALID_DECORATOR_ARGS_FILE.name, 'NoPropertyAssignment', @@ -595,7 +609,7 @@ describe('Fesm2015ReflectionHost', () => { }); it('should be an empty array if `args` property value is not an array literal', () => { - const program = makeProgram(INVALID_DECORATOR_ARGS_FILE); + const program = makeTestProgram(INVALID_DECORATOR_ARGS_FILE); const host = new Esm2015ReflectionHost(false, program.getTypeChecker()); const classNode = getDeclaration( program, INVALID_DECORATOR_ARGS_FILE.name, 'NotArrayLiteral', ts.isClassDeclaration); @@ -610,7 +624,7 @@ describe('Fesm2015ReflectionHost', () => { describe('getMembersOfClass()', () => { it('should find decorated properties on a class', () => { - const program = makeProgram(SOME_DIRECTIVE_FILE); + const program = makeTestProgram(SOME_DIRECTIVE_FILE); const host = new Esm2015ReflectionHost(false, program.getTypeChecker()); const classNode = getDeclaration(program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', ts.isClassDeclaration); @@ -628,7 +642,7 @@ describe('Fesm2015ReflectionHost', () => { }); it('should find non decorated properties on a class', () => { - const program = makeProgram(SOME_DIRECTIVE_FILE); + const program = makeTestProgram(SOME_DIRECTIVE_FILE); const host = new Esm2015ReflectionHost(false, program.getTypeChecker()); const classNode = getDeclaration(program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', ts.isClassDeclaration); @@ -642,7 +656,7 @@ describe('Fesm2015ReflectionHost', () => { }); it('should find static methods on a class', () => { - const program = makeProgram(SOME_DIRECTIVE_FILE); + const program = makeTestProgram(SOME_DIRECTIVE_FILE); const host = new Esm2015ReflectionHost(false, program.getTypeChecker()); const classNode = getDeclaration(program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', ts.isClassDeclaration); @@ -655,7 +669,7 @@ describe('Fesm2015ReflectionHost', () => { }); it('should find static properties on a class', () => { - const program = makeProgram(SOME_DIRECTIVE_FILE); + const program = makeTestProgram(SOME_DIRECTIVE_FILE); const host = new Esm2015ReflectionHost(false, program.getTypeChecker()); const classNode = getDeclaration(program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', ts.isClassDeclaration); @@ -669,7 +683,7 @@ describe('Fesm2015ReflectionHost', () => { }); it('should throw if the symbol is not a class', () => { - const program = makeProgram(FOO_FUNCTION_FILE); + const program = makeTestProgram(FOO_FUNCTION_FILE); const host = new Esm2015ReflectionHost(false, program.getTypeChecker()); const functionNode = getDeclaration(program, FOO_FUNCTION_FILE.name, 'foo', ts.isFunctionDeclaration); @@ -679,7 +693,7 @@ describe('Fesm2015ReflectionHost', () => { }); it('should return an empty array if there are no prop decorators', () => { - const program = makeProgram(SIMPLE_CLASS_FILE); + const program = makeTestProgram(SIMPLE_CLASS_FILE); const host = new Esm2015ReflectionHost(false, program.getTypeChecker()); const classNode = getDeclaration(program, SIMPLE_CLASS_FILE.name, 'EmptyClass', ts.isClassDeclaration); @@ -690,7 +704,7 @@ describe('Fesm2015ReflectionHost', () => { it('should not process decorated properties in `propDecorators` if it is not an object literal', () => { - const program = makeProgram(INVALID_PROP_DECORATORS_FILE); + const program = makeTestProgram(INVALID_PROP_DECORATORS_FILE); const host = new Esm2015ReflectionHost(false, program.getTypeChecker()); const classNode = getDeclaration( program, INVALID_PROP_DECORATORS_FILE.name, 'NotObjectLiteral', ts.isClassDeclaration); @@ -700,7 +714,7 @@ describe('Fesm2015ReflectionHost', () => { }); it('should ignore prop decorator elements that are not object literals', () => { - const program = makeProgram(INVALID_PROP_DECORATORS_FILE); + const program = makeTestProgram(INVALID_PROP_DECORATORS_FILE); const host = new Esm2015ReflectionHost(false, program.getTypeChecker()); const classNode = getDeclaration( program, INVALID_PROP_DECORATORS_FILE.name, 'NotObjectLiteralProp', @@ -714,7 +728,7 @@ describe('Fesm2015ReflectionHost', () => { }); it('should ignore prop decorator elements that have no `type` property', () => { - const program = makeProgram(INVALID_PROP_DECORATORS_FILE); + const program = makeTestProgram(INVALID_PROP_DECORATORS_FILE); const host = new Esm2015ReflectionHost(false, program.getTypeChecker()); const classNode = getDeclaration( program, INVALID_PROP_DECORATORS_FILE.name, 'NoTypeProperty', ts.isClassDeclaration); @@ -727,7 +741,7 @@ describe('Fesm2015ReflectionHost', () => { }); it('should ignore prop decorator elements whose `type` value is not an identifier', () => { - const program = makeProgram(INVALID_PROP_DECORATORS_FILE); + const program = makeTestProgram(INVALID_PROP_DECORATORS_FILE); const host = new Esm2015ReflectionHost(false, program.getTypeChecker()); const classNode = getDeclaration( program, INVALID_PROP_DECORATORS_FILE.name, 'NotIdentifier', ts.isClassDeclaration); @@ -747,7 +761,7 @@ describe('Fesm2015ReflectionHost', () => { return {name: `name${callCount}`, from: '@angular/core'}; }); - const program = makeProgram(SOME_DIRECTIVE_FILE); + const program = makeTestProgram(SOME_DIRECTIVE_FILE); const host = new Esm2015ReflectionHost(false, program.getTypeChecker()); const classNode = getDeclaration(program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', ts.isClassDeclaration); @@ -769,7 +783,7 @@ describe('Fesm2015ReflectionHost', () => { describe('(returned prop decorators `args`)', () => { it('should be an empty array if prop decorator has no `args` property', () => { - const program = makeProgram(INVALID_PROP_DECORATOR_ARGS_FILE); + const program = makeTestProgram(INVALID_PROP_DECORATOR_ARGS_FILE); const host = new Esm2015ReflectionHost(false, program.getTypeChecker()); const classNode = getDeclaration( program, INVALID_PROP_DECORATOR_ARGS_FILE.name, 'NoArgsProperty', @@ -784,7 +798,7 @@ describe('Fesm2015ReflectionHost', () => { }); it('should be an empty array if prop decorator\'s `args` has no property assignment', () => { - const program = makeProgram(INVALID_PROP_DECORATOR_ARGS_FILE); + const program = makeTestProgram(INVALID_PROP_DECORATOR_ARGS_FILE); const host = new Esm2015ReflectionHost(false, program.getTypeChecker()); const classNode = getDeclaration( program, INVALID_PROP_DECORATOR_ARGS_FILE.name, 'NoPropertyAssignment', @@ -799,7 +813,7 @@ describe('Fesm2015ReflectionHost', () => { }); it('should be an empty array if `args` property value is not an array literal', () => { - const program = makeProgram(INVALID_PROP_DECORATOR_ARGS_FILE); + const program = makeTestProgram(INVALID_PROP_DECORATOR_ARGS_FILE); const host = new Esm2015ReflectionHost(false, program.getTypeChecker()); const classNode = getDeclaration( program, INVALID_PROP_DECORATOR_ARGS_FILE.name, 'NotArrayLiteral', @@ -817,7 +831,7 @@ describe('Fesm2015ReflectionHost', () => { describe('getConstructorParameters()', () => { it('should find the decorated constructor parameters', () => { - const program = makeProgram(SOME_DIRECTIVE_FILE); + const program = makeTestProgram(SOME_DIRECTIVE_FILE); const host = new Esm2015ReflectionHost(false, program.getTypeChecker()); const classNode = getDeclaration(program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', ts.isClassDeclaration); @@ -833,7 +847,7 @@ describe('Fesm2015ReflectionHost', () => { }); it('should throw if the symbol is not a class', () => { - const program = makeProgram(FOO_FUNCTION_FILE); + const program = makeTestProgram(FOO_FUNCTION_FILE); const host = new Esm2015ReflectionHost(false, program.getTypeChecker()); const functionNode = getDeclaration(program, FOO_FUNCTION_FILE.name, 'foo', ts.isFunctionDeclaration); @@ -843,7 +857,7 @@ describe('Fesm2015ReflectionHost', () => { }); it('should return `null` if there is no constructor', () => { - const program = makeProgram(SIMPLE_CLASS_FILE); + const program = makeTestProgram(SIMPLE_CLASS_FILE); const host = new Esm2015ReflectionHost(false, program.getTypeChecker()); const classNode = getDeclaration(program, SIMPLE_CLASS_FILE.name, 'EmptyClass', ts.isClassDeclaration); @@ -852,7 +866,7 @@ describe('Fesm2015ReflectionHost', () => { }); it('should return an array even if there are no decorators', () => { - const program = makeProgram(SIMPLE_CLASS_FILE); + const program = makeTestProgram(SIMPLE_CLASS_FILE); const host = new Esm2015ReflectionHost(false, program.getTypeChecker()); const classNode = getDeclaration( program, SIMPLE_CLASS_FILE.name, 'NoDecoratorConstructorClass', ts.isClassDeclaration); @@ -865,7 +879,7 @@ describe('Fesm2015ReflectionHost', () => { }); it('should return an empty array if there are no constructor parameters', () => { - const program = makeProgram(INVALID_CTOR_DECORATORS_FILE); + const program = makeTestProgram(INVALID_CTOR_DECORATORS_FILE); const host = new Esm2015ReflectionHost(false, program.getTypeChecker()); const classNode = getDeclaration( program, INVALID_CTOR_DECORATORS_FILE.name, 'NoParameters', ts.isClassDeclaration); @@ -875,7 +889,7 @@ describe('Fesm2015ReflectionHost', () => { }); it('should ignore decorators that are not imported from core', () => { - const program = makeProgram(INVALID_CTOR_DECORATORS_FILE); + const program = makeTestProgram(INVALID_CTOR_DECORATORS_FILE); const host = new Esm2015ReflectionHost(false, program.getTypeChecker()); const classNode = getDeclaration( program, INVALID_CTOR_DECORATORS_FILE.name, 'NotFromCore', ts.isClassDeclaration); @@ -889,7 +903,7 @@ describe('Fesm2015ReflectionHost', () => { }); it('should ignore `ctorParameters` if it is not an arrow function', () => { - const program = makeProgram(INVALID_CTOR_DECORATORS_FILE); + const program = makeTestProgram(INVALID_CTOR_DECORATORS_FILE); const host = new Esm2015ReflectionHost(false, program.getTypeChecker()); const classNode = getDeclaration( program, INVALID_CTOR_DECORATORS_FILE.name, 'NotArrowFunction', ts.isClassDeclaration); @@ -903,7 +917,7 @@ describe('Fesm2015ReflectionHost', () => { }); it('should ignore `ctorParameters` if it does not return an array literal', () => { - const program = makeProgram(INVALID_CTOR_DECORATORS_FILE); + const program = makeTestProgram(INVALID_CTOR_DECORATORS_FILE); const host = new Esm2015ReflectionHost(false, program.getTypeChecker()); const classNode = getDeclaration( program, INVALID_CTOR_DECORATORS_FILE.name, 'NotArrayLiteral', ts.isClassDeclaration); @@ -918,7 +932,7 @@ describe('Fesm2015ReflectionHost', () => { describe('(returned parameters `decorators`)', () => { it('should ignore param decorator elements that are not object literals', () => { - const program = makeProgram(INVALID_CTOR_DECORATORS_FILE); + const program = makeTestProgram(INVALID_CTOR_DECORATORS_FILE); const host = new Esm2015ReflectionHost(false, program.getTypeChecker()); const classNode = getDeclaration( program, INVALID_CTOR_DECORATORS_FILE.name, 'NotObjectLiteral', ts.isClassDeclaration); @@ -936,7 +950,7 @@ describe('Fesm2015ReflectionHost', () => { }); it('should ignore param decorator elements that have no `type` property', () => { - const program = makeProgram(INVALID_CTOR_DECORATORS_FILE); + const program = makeTestProgram(INVALID_CTOR_DECORATORS_FILE); const host = new Esm2015ReflectionHost(false, program.getTypeChecker()); const classNode = getDeclaration( program, INVALID_CTOR_DECORATORS_FILE.name, 'NoTypeProperty', ts.isClassDeclaration); @@ -948,7 +962,7 @@ describe('Fesm2015ReflectionHost', () => { }); it('should ignore param decorator elements whose `type` value is not an identifier', () => { - const program = makeProgram(INVALID_CTOR_DECORATORS_FILE); + const program = makeTestProgram(INVALID_CTOR_DECORATORS_FILE); const host = new Esm2015ReflectionHost(false, program.getTypeChecker()); const classNode = getDeclaration( program, INVALID_CTOR_DECORATORS_FILE.name, 'NotIdentifier', ts.isClassDeclaration); @@ -964,7 +978,7 @@ describe('Fesm2015ReflectionHost', () => { const spy = spyOn(Esm2015ReflectionHost.prototype, 'getImportOfIdentifier') .and.returnValue(mockImportInfo); - const program = makeProgram(SOME_DIRECTIVE_FILE); + const program = makeTestProgram(SOME_DIRECTIVE_FILE); const host = new Esm2015ReflectionHost(false, program.getTypeChecker()); const classNode = getDeclaration( program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', ts.isClassDeclaration); @@ -981,7 +995,7 @@ describe('Fesm2015ReflectionHost', () => { describe('(returned parameters `decorators.args`)', () => { it('should be an empty array if param decorator has no `args` property', () => { - const program = makeProgram(INVALID_CTOR_DECORATOR_ARGS_FILE); + const program = makeTestProgram(INVALID_CTOR_DECORATOR_ARGS_FILE); const host = new Esm2015ReflectionHost(false, program.getTypeChecker()); const classNode = getDeclaration( program, INVALID_CTOR_DECORATOR_ARGS_FILE.name, 'NoArgsProperty', @@ -996,7 +1010,7 @@ describe('Fesm2015ReflectionHost', () => { }); it('should be an empty array if param decorator\'s `args` has no property assignment', () => { - const program = makeProgram(INVALID_CTOR_DECORATOR_ARGS_FILE); + const program = makeTestProgram(INVALID_CTOR_DECORATOR_ARGS_FILE); const host = new Esm2015ReflectionHost(false, program.getTypeChecker()); const classNode = getDeclaration( program, INVALID_CTOR_DECORATOR_ARGS_FILE.name, 'NoPropertyAssignment', @@ -1010,7 +1024,7 @@ describe('Fesm2015ReflectionHost', () => { }); it('should be an empty array if `args` property value is not an array literal', () => { - const program = makeProgram(INVALID_CTOR_DECORATOR_ARGS_FILE); + const program = makeTestProgram(INVALID_CTOR_DECORATOR_ARGS_FILE); const host = new Esm2015ReflectionHost(false, program.getTypeChecker()); const classNode = getDeclaration( program, INVALID_CTOR_DECORATOR_ARGS_FILE.name, 'NotArrayLiteral', @@ -1027,7 +1041,7 @@ describe('Fesm2015ReflectionHost', () => { describe('getDefinitionOfFunction()', () => { it('should return an object describing the function declaration passed as an argument', () => { - const program = makeProgram(FUNCTION_BODY_FILE); + const program = makeTestProgram(FUNCTION_BODY_FILE); const host = new Esm2015ReflectionHost(false, program.getTypeChecker()); const fooNode = @@ -1089,7 +1103,7 @@ describe('Fesm2015ReflectionHost', () => { describe('getImportOfIdentifier()', () => { it('should find the import of an identifier', () => { - const program = makeProgram(...IMPORTS_FILES); + const program = makeTestProgram(...IMPORTS_FILES); const host = new Esm2015ReflectionHost(false, program.getTypeChecker()); const variableNode = getDeclaration(program, IMPORTS_FILES[1].name, 'b', ts.isVariableDeclaration); @@ -1099,7 +1113,7 @@ describe('Fesm2015ReflectionHost', () => { }); it('should find the name by which the identifier was exported, not imported', () => { - const program = makeProgram(...IMPORTS_FILES); + const program = makeTestProgram(...IMPORTS_FILES); const host = new Esm2015ReflectionHost(false, program.getTypeChecker()); const variableNode = getDeclaration(program, IMPORTS_FILES[1].name, 'c', ts.isVariableDeclaration); @@ -1109,7 +1123,7 @@ describe('Fesm2015ReflectionHost', () => { }); it('should return null if the identifier was not imported', () => { - const program = makeProgram(...IMPORTS_FILES); + const program = makeTestProgram(...IMPORTS_FILES); const host = new Esm2015ReflectionHost(false, program.getTypeChecker()); const variableNode = getDeclaration(program, IMPORTS_FILES[1].name, 'd', ts.isVariableDeclaration); @@ -1121,7 +1135,7 @@ describe('Fesm2015ReflectionHost', () => { describe('getDeclarationOfIdentifier()', () => { it('should return the declaration of a locally defined identifier', () => { - const program = makeProgram(SOME_DIRECTIVE_FILE); + const program = makeTestProgram(SOME_DIRECTIVE_FILE); const host = new Esm2015ReflectionHost(false, program.getTypeChecker()); const classNode = getDeclaration(program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', ts.isClassDeclaration); @@ -1137,7 +1151,7 @@ describe('Fesm2015ReflectionHost', () => { }); it('should return the declaration of an externally defined identifier', () => { - const program = makeProgram(SOME_DIRECTIVE_FILE); + const program = makeTestProgram(SOME_DIRECTIVE_FILE); const host = new Esm2015ReflectionHost(false, program.getTypeChecker()); const classNode = getDeclaration(program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', ts.isClassDeclaration); @@ -1157,7 +1171,7 @@ describe('Fesm2015ReflectionHost', () => { describe('getExportsOfModule()', () => { it('should return a map of all the exports from a given module', () => { - const program = makeProgram(...EXPORTS_FILES); + const program = makeTestProgram(...EXPORTS_FILES); const host = new Esm2015ReflectionHost(false, program.getTypeChecker()); const file = program.getSourceFile(EXPORTS_FILES[1].name) !; const exportDeclarations = host.getExportsOfModule(file); @@ -1192,7 +1206,7 @@ describe('Fesm2015ReflectionHost', () => { describe('isClass()', () => { it('should return true if a given node is a TS class declaration', () => { - const program = makeProgram(SIMPLE_CLASS_FILE); + const program = makeTestProgram(SIMPLE_CLASS_FILE); const host = new Esm2015ReflectionHost(false, program.getTypeChecker()); const node = getDeclaration(program, SIMPLE_CLASS_FILE.name, 'EmptyClass', ts.isClassDeclaration); @@ -1200,7 +1214,7 @@ describe('Fesm2015ReflectionHost', () => { }); it('should return false if a given node is a TS function declaration', () => { - const program = makeProgram(FOO_FUNCTION_FILE); + const program = makeTestProgram(FOO_FUNCTION_FILE); const host = new Esm2015ReflectionHost(false, program.getTypeChecker()); const node = getDeclaration(program, FOO_FUNCTION_FILE.name, 'foo', ts.isFunctionDeclaration); expect(host.isClass(node)).toBe(false); @@ -1209,10 +1223,10 @@ describe('Fesm2015ReflectionHost', () => { describe('getGenericArityOfClass()', () => { it('should properly count type parameters', () => { - const dtsProgram = makeProgram(ARITY_CLASSES[1]); - const program = makeProgram(ARITY_CLASSES[0]); - const host = new Esm2015ReflectionHost( - false, program.getTypeChecker(), ARITY_CLASSES[1].name, dtsProgram); + const program = makeTestProgram(ARITY_CLASSES[0]); + const dtsProgram = makeTestProgram(ARITY_CLASSES[1]); + const dts = makeTestBundleProgram([ARITY_CLASSES[1]]); + const host = new Esm2015ReflectionHost(false, program.getTypeChecker(), dts); const noTypeParamClass = getDeclaration(program, '/src/class.js', 'NoTypeParam', ts.isClassDeclaration); expect(host.getGenericArityOfClass(noTypeParamClass)).toBe(0); @@ -1228,7 +1242,7 @@ describe('Fesm2015ReflectionHost', () => { describe('getSwitchableDeclarations()', () => { it('should return a collection of all the switchable variable declarations in the given module', () => { - const program = makeProgram(MARKER_FILE); + const program = makeTestProgram(MARKER_FILE); const host = new Esm2015ReflectionHost(false, program.getTypeChecker()); const file = program.getSourceFile(MARKER_FILE.name) !; const declarations = host.getSwitchableDeclarations(file); @@ -1240,7 +1254,7 @@ describe('Fesm2015ReflectionHost', () => { describe('findDecoratedClasses()', () => { it('should return an array of all decorated classes in the given source file', () => { - const program = makeProgram(...DECORATED_FILES); + const program = makeTestProgram(...DECORATED_FILES); const host = new Esm2015ReflectionHost(false, program.getTypeChecker()); const primaryFile = program.getSourceFile(DECORATED_FILES[0].name) !; const secondaryFile = program.getSourceFile(DECORATED_FILES[1].name) !; @@ -1267,66 +1281,86 @@ describe('Fesm2015ReflectionHost', () => { describe('getDtsDeclarationsOfClass()', () => { it('should find the dts declaration that has the same relative path to the source file', () => { - const srcProgram = makeProgram(...TYPINGS_SRC_FILES); - const dtsProgram = makeProgram(...TYPINGS_DTS_FILES); + const srcProgram = makeTestProgram(...TYPINGS_SRC_FILES); + const dts = makeTestBundleProgram(TYPINGS_DTS_FILES); const class1 = getDeclaration(srcProgram, '/src/class1.js', 'Class1', ts.isClassDeclaration); - const host = new Esm2015ReflectionHost( - false, srcProgram.getTypeChecker(), TYPINGS_DTS_FILES[0].name, dtsProgram); + const host = new Esm2015ReflectionHost(false, srcProgram.getTypeChecker(), dts); const dtsDeclaration = host.getDtsDeclarationOfClass(class1); expect(dtsDeclaration !.getSourceFile().fileName).toEqual('/typings/class1.d.ts'); }); - it('should throw an error if there is no matching class in the matching dts file', () => { - const srcProgram = makeProgram(...TYPINGS_SRC_FILES); - const dtsProgram = makeProgram(...TYPINGS_DTS_FILES); + it('should return null if there is no matching class in the matching dts file', () => { + const srcProgram = makeTestProgram(...TYPINGS_SRC_FILES); + const dts = makeTestBundleProgram(TYPINGS_DTS_FILES); const missingClass = getDeclaration(srcProgram, '/src/class1.js', 'MissingClass1', ts.isClassDeclaration); - const host = new Esm2015ReflectionHost( - false, srcProgram.getTypeChecker(), TYPINGS_DTS_FILES[0].name, dtsProgram); + const host = new Esm2015ReflectionHost(false, srcProgram.getTypeChecker(), dts); - expect(() => host.getDtsDeclarationOfClass(missingClass)) - .toThrowError( - 'Unable to find matching typings (.d.ts) declaration for MissingClass1 in /src/class1.js'); + expect(host.getDtsDeclarationOfClass(missingClass)).toBe(null); }); - it('should throw an error if there is no matching dts file', () => { - const srcProgram = makeProgram(...TYPINGS_SRC_FILES); - const dtsProgram = makeProgram(...TYPINGS_DTS_FILES); + it('should return null if there is no matching dts file', () => { + const srcProgram = makeTestProgram(...TYPINGS_SRC_FILES); + const dts = makeTestBundleProgram(TYPINGS_DTS_FILES); const missingClass = getDeclaration( srcProgram, '/src/missing-class.js', 'MissingClass2', ts.isClassDeclaration); - const host = new Esm2015ReflectionHost( - false, srcProgram.getTypeChecker(), TYPINGS_DTS_FILES[0].name, dtsProgram); + const host = new Esm2015ReflectionHost(false, srcProgram.getTypeChecker(), dts); - expect(() => host.getDtsDeclarationOfClass(missingClass)) - .toThrowError( - 'Unable to find matching typings (.d.ts) declaration for MissingClass2 in /src/missing-class.js'); + expect(host.getDtsDeclarationOfClass(missingClass)).toBe(null); }); it('should find the dts file that contains a matching class declaration, even if the source files do not match', () => { - const srcProgram = makeProgram(...TYPINGS_SRC_FILES); - const dtsProgram = makeProgram(...TYPINGS_DTS_FILES); + const srcProgram = makeTestProgram(...TYPINGS_SRC_FILES); + const dts = makeTestBundleProgram(TYPINGS_DTS_FILES); const class1 = getDeclaration(srcProgram, '/src/flat-file.js', 'Class1', ts.isClassDeclaration); - const host = new Esm2015ReflectionHost( - false, srcProgram.getTypeChecker(), TYPINGS_DTS_FILES[0].name, dtsProgram); + const host = new Esm2015ReflectionHost(false, srcProgram.getTypeChecker(), dts); const dtsDeclaration = host.getDtsDeclarationOfClass(class1); expect(dtsDeclaration !.getSourceFile().fileName).toEqual('/typings/class1.d.ts'); }); it('should find aliased exports', () => { - const srcProgram = makeProgram(...TYPINGS_SRC_FILES); - const dtsProgram = makeProgram(...TYPINGS_DTS_FILES); + const srcProgram = makeTestProgram(...TYPINGS_SRC_FILES); + const dts = makeTestBundleProgram(TYPINGS_DTS_FILES); const class3 = getDeclaration(srcProgram, '/src/flat-file.js', 'Class3', ts.isClassDeclaration); - const host = new Esm2015ReflectionHost( - false, srcProgram.getTypeChecker(), TYPINGS_DTS_FILES[0].name, dtsProgram); + const host = new Esm2015ReflectionHost(false, srcProgram.getTypeChecker(), dts); const dtsDeclaration = host.getDtsDeclarationOfClass(class3); expect(dtsDeclaration !.getSourceFile().fileName).toEqual('/typings/class3.d.ts'); }); - }); + it('should find the dts file that contains a matching class declaration, even if the class is not publicly exported', + () => { + const srcProgram = makeTestProgram(...TYPINGS_SRC_FILES); + const dts = makeTestBundleProgram(TYPINGS_DTS_FILES); + const internalClass = + getDeclaration(srcProgram, '/src/internal.js', 'InternalClass', ts.isClassDeclaration); + const host = new Esm2015ReflectionHost(false, srcProgram.getTypeChecker(), dts); + + const dtsDeclaration = host.getDtsDeclarationOfClass(internalClass); + expect(dtsDeclaration !.getSourceFile().fileName).toEqual('/typings/internal.d.ts'); + }); + + it('should prefer the publicly exported class if there are multiple classes with the same name', + () => { + const srcProgram = makeTestProgram(...TYPINGS_SRC_FILES); + const dts = makeTestBundleProgram(TYPINGS_DTS_FILES); + const class2 = + getDeclaration(srcProgram, '/src/class2.js', 'Class2', ts.isClassDeclaration); + const internalClass2 = + getDeclaration(srcProgram, '/src/internal.js', 'Class2', ts.isClassDeclaration); + const host = new Esm2015ReflectionHost(false, srcProgram.getTypeChecker(), dts); + + const class2DtsDeclaration = host.getDtsDeclarationOfClass(class2); + expect(class2DtsDeclaration !.getSourceFile().fileName).toEqual('/typings/class2.d.ts'); + + const internalClass2DtsDeclaration = host.getDtsDeclarationOfClass(internalClass2); + expect(internalClass2DtsDeclaration !.getSourceFile().fileName) + .toEqual('/typings/class2.d.ts'); + }); + }); }); diff --git a/packages/compiler-cli/src/ngcc/test/host/esm5_host_import_helper_spec.ts b/packages/compiler-cli/src/ngcc/test/host/esm5_host_import_helper_spec.ts index 604425af9b46..e696d5675f28 100644 --- a/packages/compiler-cli/src/ngcc/test/host/esm5_host_import_helper_spec.ts +++ b/packages/compiler-cli/src/ngcc/test/host/esm5_host_import_helper_spec.ts @@ -10,7 +10,7 @@ import * as ts from 'typescript'; import {ClassMemberKind, Import} from '../../../ngtsc/host'; import {Esm5ReflectionHost} from '../../src/host/esm5_host'; -import {convertToDirectTsLibImport, getDeclaration, makeProgram} from '../helpers/utils'; +import {convertToDirectTsLibImport, getDeclaration, makeTestProgram} from '../helpers/utils'; const FILES = [ { @@ -118,7 +118,7 @@ describe('Esm5ReflectionHost [import helper style]', () => { describe('getDecoratorsOfDeclaration()', () => { it('should find the decorators on a class', () => { - const program = makeProgram(fileSystem.files[0]); + const program = makeTestProgram(fileSystem.files[0]); const host = new Esm5ReflectionHost(false, program.getTypeChecker()); const classNode = getDeclaration( program, '/some_directive.js', 'SomeDirective', ts.isVariableDeclaration); @@ -136,14 +136,13 @@ describe('Esm5ReflectionHost [import helper style]', () => { }); it('should use `getImportOfIdentifier()` to retrieve import info', () => { - const mockImportInfo = {} as Import; const spy = spyOn(Esm5ReflectionHost.prototype, 'getImportOfIdentifier') .and.callFake( (identifier: ts.Identifier) => identifier.getText() === 'Directive' ? {from: '@angular/core', name: 'Directive'} : {}); - const program = makeProgram(fileSystem.files[0]); + const program = makeTestProgram(fileSystem.files[0]); const host = new Esm5ReflectionHost(false, program.getTypeChecker()); const classNode = getDeclaration( program, '/some_directive.js', 'SomeDirective', ts.isVariableDeclaration); @@ -158,7 +157,7 @@ describe('Esm5ReflectionHost [import helper style]', () => { }); it('should support decorators being used inside @angular/core', () => { - const program = makeProgram(fileSystem.files[1]); + const program = makeTestProgram(fileSystem.files[1]); const host = new Esm5ReflectionHost(true, program.getTypeChecker()); const classNode = getDeclaration( program, '/node_modules/@angular/core/some_directive.js', 'SomeDirective', @@ -179,7 +178,7 @@ describe('Esm5ReflectionHost [import helper style]', () => { describe('getMembersOfClass()', () => { it('should find decorated members on a class', () => { - const program = makeProgram(fileSystem.files[0]); + const program = makeTestProgram(fileSystem.files[0]); const host = new Esm5ReflectionHost(false, program.getTypeChecker()); const classNode = getDeclaration( program, '/some_directive.js', 'SomeDirective', ts.isVariableDeclaration); @@ -197,7 +196,7 @@ describe('Esm5ReflectionHost [import helper style]', () => { }); it('should find non decorated properties on a class', () => { - const program = makeProgram(fileSystem.files[0]); + const program = makeTestProgram(fileSystem.files[0]); const host = new Esm5ReflectionHost(false, program.getTypeChecker()); const classNode = getDeclaration( program, '/some_directive.js', 'SomeDirective', ts.isVariableDeclaration); @@ -211,7 +210,7 @@ describe('Esm5ReflectionHost [import helper style]', () => { }); it('should find static methods on a class', () => { - const program = makeProgram(fileSystem.files[0]); + const program = makeTestProgram(fileSystem.files[0]); const host = new Esm5ReflectionHost(false, program.getTypeChecker()); const classNode = getDeclaration( program, '/some_directive.js', 'SomeDirective', ts.isVariableDeclaration); @@ -224,7 +223,7 @@ describe('Esm5ReflectionHost [import helper style]', () => { }); it('should find static properties on a class', () => { - const program = makeProgram(fileSystem.files[0]); + const program = makeTestProgram(fileSystem.files[0]); const host = new Esm5ReflectionHost(false, program.getTypeChecker()); const classNode = getDeclaration( program, '/some_directive.js', 'SomeDirective', ts.isVariableDeclaration); @@ -241,7 +240,7 @@ describe('Esm5ReflectionHost [import helper style]', () => { const spy = spyOn(Esm5ReflectionHost.prototype, 'getImportOfIdentifier').and.returnValue({}); - const program = makeProgram(fileSystem.files[0]); + const program = makeTestProgram(fileSystem.files[0]); const host = new Esm5ReflectionHost(false, program.getTypeChecker()); const classNode = getDeclaration( program, '/some_directive.js', 'SomeDirective', ts.isVariableDeclaration); @@ -252,7 +251,7 @@ describe('Esm5ReflectionHost [import helper style]', () => { }); it('should support decorators being used inside @angular/core', () => { - const program = makeProgram(fileSystem.files[1]); + const program = makeTestProgram(fileSystem.files[1]); const host = new Esm5ReflectionHost(true, program.getTypeChecker()); const classNode = getDeclaration( program, '/node_modules/@angular/core/some_directive.js', 'SomeDirective', @@ -268,7 +267,7 @@ describe('Esm5ReflectionHost [import helper style]', () => { describe('getConstructorParameters', () => { it('should find the decorated constructor parameters', () => { - const program = makeProgram(fileSystem.files[0]); + const program = makeTestProgram(fileSystem.files[0]); const host = new Esm5ReflectionHost(false, program.getTypeChecker()); const classNode = getDeclaration( program, '/some_directive.js', 'SomeDirective', ts.isVariableDeclaration); @@ -289,7 +288,7 @@ describe('Esm5ReflectionHost [import helper style]', () => { const spy = spyOn(Esm5ReflectionHost.prototype, 'getImportOfIdentifier') .and.returnValue(mockImportInfo); - const program = makeProgram(fileSystem.files[0]); + const program = makeTestProgram(fileSystem.files[0]); const host = new Esm5ReflectionHost(false, program.getTypeChecker()); const classNode = getDeclaration( program, '/some_directive.js', 'SomeDirective', ts.isVariableDeclaration); @@ -307,7 +306,7 @@ describe('Esm5ReflectionHost [import helper style]', () => { describe('getDeclarationOfIdentifier', () => { it('should return the declaration of a locally defined identifier', () => { - const program = makeProgram(fileSystem.files[0]); + const program = makeTestProgram(fileSystem.files[0]); const host = new Esm5ReflectionHost(false, program.getTypeChecker()); const classNode = getDeclaration( program, '/some_directive.js', 'SomeDirective', ts.isVariableDeclaration); @@ -323,7 +322,7 @@ describe('Esm5ReflectionHost [import helper style]', () => { }); it('should return the declaration of an externally defined identifier', () => { - const program = makeProgram(fileSystem.files[0]); + const program = makeTestProgram(fileSystem.files[0]); const host = new Esm5ReflectionHost(false, program.getTypeChecker()); const classNode = getDeclaration( program, '/some_directive.js', 'SomeDirective', ts.isVariableDeclaration); @@ -348,7 +347,7 @@ describe('Esm5ReflectionHost [import helper style]', () => { describe('getVariableValue', () => { it('should find the "actual" declaration of an aliased variable identifier', () => { - const program = makeProgram(fileSystem.files[2]); + const program = makeTestProgram(fileSystem.files[2]); const host = new Esm5ReflectionHost(false, program.getTypeChecker()); const ngModuleRef = findVariableDeclaration( program.getSourceFile(fileSystem.files[2].name) !, 'HttpClientXsrfModule_1'); @@ -363,7 +362,7 @@ describe('Esm5ReflectionHost [import helper style]', () => { }); it('should return undefined if the variable has no assignment', () => { - const program = makeProgram(fileSystem.files[2]); + const program = makeTestProgram(fileSystem.files[2]); const host = new Esm5ReflectionHost(false, program.getTypeChecker()); const missingValue = findVariableDeclaration( program.getSourceFile(fileSystem.files[2].name) !, 'missingValue'); @@ -372,7 +371,7 @@ describe('Esm5ReflectionHost [import helper style]', () => { }); it('should return null if the variable is not assigned from a call to __decorate', () => { - const program = makeProgram(fileSystem.files[2]); + const program = makeTestProgram(fileSystem.files[2]); const host = new Esm5ReflectionHost(false, program.getTypeChecker()); const nonDecoratedVar = findVariableDeclaration( program.getSourceFile(fileSystem.files[2].name) !, 'nonDecoratedVar'); diff --git a/packages/compiler-cli/src/ngcc/test/host/esm5_host_spec.ts b/packages/compiler-cli/src/ngcc/test/host/esm5_host_spec.ts index 4c6ddd00144d..6479edecd456 100644 --- a/packages/compiler-cli/src/ngcc/test/host/esm5_host_spec.ts +++ b/packages/compiler-cli/src/ngcc/test/host/esm5_host_spec.ts @@ -11,7 +11,7 @@ import * as ts from 'typescript'; import {ClassMemberKind, Import} from '../../../ngtsc/host'; import {Esm2015ReflectionHost} from '../../src/host/esm2015_host'; import {Esm5ReflectionHost} from '../../src/host/esm5_host'; -import {getDeclaration, makeProgram} from '../helpers/utils'; +import {getDeclaration, makeTestProgram} from '../helpers/utils'; const SOME_DIRECTIVE_FILE = { name: '/some_directive.js', @@ -476,7 +476,7 @@ describe('Esm5ReflectionHost', () => { describe('getDecoratorsOfDeclaration()', () => { it('should find the decorators on a class', () => { - const program = makeProgram(SOME_DIRECTIVE_FILE); + const program = makeTestProgram(SOME_DIRECTIVE_FILE); const host = new Esm5ReflectionHost(false, program.getTypeChecker()); const classNode = getDeclaration( program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', ts.isVariableDeclaration); @@ -494,7 +494,7 @@ describe('Esm5ReflectionHost', () => { }); it('should return null if the symbol is not a class', () => { - const program = makeProgram(FOO_FUNCTION_FILE); + const program = makeTestProgram(FOO_FUNCTION_FILE); const host = new Esm5ReflectionHost(false, program.getTypeChecker()); const functionNode = getDeclaration(program, FOO_FUNCTION_FILE.name, 'foo', ts.isFunctionDeclaration); @@ -503,7 +503,7 @@ describe('Esm5ReflectionHost', () => { }); it('should return null if there are no decorators', () => { - const program = makeProgram(SIMPLE_CLASS_FILE); + const program = makeTestProgram(SIMPLE_CLASS_FILE); const host = new Esm5ReflectionHost(false, program.getTypeChecker()); const classNode = getDeclaration(program, SIMPLE_CLASS_FILE.name, 'EmptyClass', ts.isVariableDeclaration); @@ -512,7 +512,7 @@ describe('Esm5ReflectionHost', () => { }); it('should ignore `decorators` if it is not an array literal', () => { - const program = makeProgram(INVALID_DECORATORS_FILE); + const program = makeTestProgram(INVALID_DECORATORS_FILE); const host = new Esm5ReflectionHost(false, program.getTypeChecker()); const classNode = getDeclaration( program, INVALID_DECORATORS_FILE.name, 'NotArrayLiteral', ts.isVariableDeclaration); @@ -521,7 +521,7 @@ describe('Esm5ReflectionHost', () => { }); it('should ignore decorator elements that are not object literals', () => { - const program = makeProgram(INVALID_DECORATORS_FILE); + const program = makeTestProgram(INVALID_DECORATORS_FILE); const host = new Esm5ReflectionHost(false, program.getTypeChecker()); const classNode = getDeclaration( program, INVALID_DECORATORS_FILE.name, 'NotObjectLiteral', ts.isVariableDeclaration); @@ -532,7 +532,7 @@ describe('Esm5ReflectionHost', () => { }); it('should ignore decorator elements that have no `type` property', () => { - const program = makeProgram(INVALID_DECORATORS_FILE); + const program = makeTestProgram(INVALID_DECORATORS_FILE); const host = new Esm5ReflectionHost(false, program.getTypeChecker()); const classNode = getDeclaration( program, INVALID_DECORATORS_FILE.name, 'NoTypeProperty', ts.isVariableDeclaration); @@ -543,7 +543,7 @@ describe('Esm5ReflectionHost', () => { }); it('should ignore decorator elements whose `type` value is not an identifier', () => { - const program = makeProgram(INVALID_DECORATORS_FILE); + const program = makeTestProgram(INVALID_DECORATORS_FILE); const host = new Esm5ReflectionHost(false, program.getTypeChecker()); const classNode = getDeclaration( program, INVALID_DECORATORS_FILE.name, 'NotIdentifier', ts.isVariableDeclaration); @@ -558,7 +558,7 @@ describe('Esm5ReflectionHost', () => { const spy = spyOn(Esm5ReflectionHost.prototype, 'getImportOfIdentifier') .and.returnValue(mockImportInfo); - const program = makeProgram(SOME_DIRECTIVE_FILE); + const program = makeTestProgram(SOME_DIRECTIVE_FILE); const host = new Esm5ReflectionHost(false, program.getTypeChecker()); const classNode = getDeclaration( program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', ts.isVariableDeclaration); @@ -573,7 +573,7 @@ describe('Esm5ReflectionHost', () => { describe('(returned decorators `args`)', () => { it('should be an empty array if decorator has no `args` property', () => { - const program = makeProgram(INVALID_DECORATOR_ARGS_FILE); + const program = makeTestProgram(INVALID_DECORATOR_ARGS_FILE); const host = new Esm5ReflectionHost(false, program.getTypeChecker()); const classNode = getDeclaration( program, INVALID_DECORATOR_ARGS_FILE.name, 'NoArgsProperty', ts.isVariableDeclaration); @@ -585,7 +585,7 @@ describe('Esm5ReflectionHost', () => { }); it('should be an empty array if decorator\'s `args` has no property assignment', () => { - const program = makeProgram(INVALID_DECORATOR_ARGS_FILE); + const program = makeTestProgram(INVALID_DECORATOR_ARGS_FILE); const host = new Esm5ReflectionHost(false, program.getTypeChecker()); const classNode = getDeclaration( program, INVALID_DECORATOR_ARGS_FILE.name, 'NoPropertyAssignment', @@ -598,7 +598,7 @@ describe('Esm5ReflectionHost', () => { }); it('should be an empty array if `args` property value is not an array literal', () => { - const program = makeProgram(INVALID_DECORATOR_ARGS_FILE); + const program = makeTestProgram(INVALID_DECORATOR_ARGS_FILE); const host = new Esm5ReflectionHost(false, program.getTypeChecker()); const classNode = getDeclaration( program, INVALID_DECORATOR_ARGS_FILE.name, 'NotArrayLiteral', ts.isVariableDeclaration); @@ -613,7 +613,7 @@ describe('Esm5ReflectionHost', () => { describe('getMembersOfClass()', () => { it('should find decorated members on a class', () => { - const program = makeProgram(SOME_DIRECTIVE_FILE); + const program = makeTestProgram(SOME_DIRECTIVE_FILE); const host = new Esm5ReflectionHost(false, program.getTypeChecker()); const classNode = getDeclaration( program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', ts.isVariableDeclaration); @@ -631,7 +631,7 @@ describe('Esm5ReflectionHost', () => { }); it('should find non decorated properties on a class', () => { - const program = makeProgram(SOME_DIRECTIVE_FILE); + const program = makeTestProgram(SOME_DIRECTIVE_FILE); const host = new Esm5ReflectionHost(false, program.getTypeChecker()); const classNode = getDeclaration( program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', ts.isVariableDeclaration); @@ -645,7 +645,7 @@ describe('Esm5ReflectionHost', () => { }); it('should find static methods on a class', () => { - const program = makeProgram(SOME_DIRECTIVE_FILE); + const program = makeTestProgram(SOME_DIRECTIVE_FILE); const host = new Esm5ReflectionHost(false, program.getTypeChecker()); const classNode = getDeclaration( program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', ts.isVariableDeclaration); @@ -658,7 +658,7 @@ describe('Esm5ReflectionHost', () => { }); it('should find static properties on a class', () => { - const program = makeProgram(SOME_DIRECTIVE_FILE); + const program = makeTestProgram(SOME_DIRECTIVE_FILE); const host = new Esm5ReflectionHost(false, program.getTypeChecker()); const classNode = getDeclaration( program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', ts.isVariableDeclaration); @@ -672,7 +672,7 @@ describe('Esm5ReflectionHost', () => { }); it('should throw if the symbol is not a class', () => { - const program = makeProgram(FOO_FUNCTION_FILE); + const program = makeTestProgram(FOO_FUNCTION_FILE); const host = new Esm5ReflectionHost(false, program.getTypeChecker()); const functionNode = getDeclaration(program, FOO_FUNCTION_FILE.name, 'foo', ts.isFunctionDeclaration); @@ -682,7 +682,7 @@ describe('Esm5ReflectionHost', () => { }); it('should return an empty array if there are no prop decorators', () => { - const program = makeProgram(SIMPLE_CLASS_FILE); + const program = makeTestProgram(SIMPLE_CLASS_FILE); const host = new Esm5ReflectionHost(false, program.getTypeChecker()); const classNode = getDeclaration(program, SIMPLE_CLASS_FILE.name, 'EmptyClass', ts.isVariableDeclaration); @@ -693,7 +693,7 @@ describe('Esm5ReflectionHost', () => { it('should not process decorated properties in `propDecorators` if it is not an object literal', () => { - const program = makeProgram(INVALID_PROP_DECORATORS_FILE); + const program = makeTestProgram(INVALID_PROP_DECORATORS_FILE); const host = new Esm5ReflectionHost(false, program.getTypeChecker()); const classNode = getDeclaration( program, INVALID_PROP_DECORATORS_FILE.name, 'NotObjectLiteral', @@ -704,7 +704,7 @@ describe('Esm5ReflectionHost', () => { }); it('should ignore prop decorator elements that are not object literals', () => { - const program = makeProgram(INVALID_PROP_DECORATORS_FILE); + const program = makeTestProgram(INVALID_PROP_DECORATORS_FILE); const host = new Esm5ReflectionHost(false, program.getTypeChecker()); const classNode = getDeclaration( program, INVALID_PROP_DECORATORS_FILE.name, 'NotObjectLiteralProp', @@ -718,7 +718,7 @@ describe('Esm5ReflectionHost', () => { }); it('should ignore prop decorator elements that have no `type` property', () => { - const program = makeProgram(INVALID_PROP_DECORATORS_FILE); + const program = makeTestProgram(INVALID_PROP_DECORATORS_FILE); const host = new Esm5ReflectionHost(false, program.getTypeChecker()); const classNode = getDeclaration( program, INVALID_PROP_DECORATORS_FILE.name, 'NoTypeProperty', ts.isVariableDeclaration); @@ -731,7 +731,7 @@ describe('Esm5ReflectionHost', () => { }); it('should ignore prop decorator elements whose `type` value is not an identifier', () => { - const program = makeProgram(INVALID_PROP_DECORATORS_FILE); + const program = makeTestProgram(INVALID_PROP_DECORATORS_FILE); const host = new Esm5ReflectionHost(false, program.getTypeChecker()); const classNode = getDeclaration( program, INVALID_PROP_DECORATORS_FILE.name, 'NotIdentifier', ts.isVariableDeclaration); @@ -750,7 +750,7 @@ describe('Esm5ReflectionHost', () => { return {name: `name${callCount}`, from: `@angular/core`}; }); - const program = makeProgram(SOME_DIRECTIVE_FILE); + const program = makeTestProgram(SOME_DIRECTIVE_FILE); const host = new Esm5ReflectionHost(false, program.getTypeChecker()); const classNode = getDeclaration( program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', ts.isVariableDeclaration); @@ -766,7 +766,7 @@ describe('Esm5ReflectionHost', () => { describe('(returned prop decorators `args`)', () => { it('should be an empty array if prop decorator has no `args` property', () => { - const program = makeProgram(INVALID_PROP_DECORATOR_ARGS_FILE); + const program = makeTestProgram(INVALID_PROP_DECORATOR_ARGS_FILE); const host = new Esm5ReflectionHost(false, program.getTypeChecker()); const classNode = getDeclaration( program, INVALID_PROP_DECORATOR_ARGS_FILE.name, 'NoArgsProperty', @@ -781,7 +781,7 @@ describe('Esm5ReflectionHost', () => { }); it('should be an empty array if prop decorator\'s `args` has no property assignment', () => { - const program = makeProgram(INVALID_PROP_DECORATOR_ARGS_FILE); + const program = makeTestProgram(INVALID_PROP_DECORATOR_ARGS_FILE); const host = new Esm5ReflectionHost(false, program.getTypeChecker()); const classNode = getDeclaration( program, INVALID_PROP_DECORATOR_ARGS_FILE.name, 'NoPropertyAssignment', @@ -796,7 +796,7 @@ describe('Esm5ReflectionHost', () => { }); it('should be an empty array if `args` property value is not an array literal', () => { - const program = makeProgram(INVALID_PROP_DECORATOR_ARGS_FILE); + const program = makeTestProgram(INVALID_PROP_DECORATOR_ARGS_FILE); const host = new Esm5ReflectionHost(false, program.getTypeChecker()); const classNode = getDeclaration( program, INVALID_PROP_DECORATOR_ARGS_FILE.name, 'NotArrayLiteral', @@ -814,7 +814,7 @@ describe('Esm5ReflectionHost', () => { describe('getConstructorParameters', () => { it('should find the decorated constructor parameters', () => { - const program = makeProgram(SOME_DIRECTIVE_FILE); + const program = makeTestProgram(SOME_DIRECTIVE_FILE); const host = new Esm5ReflectionHost(false, program.getTypeChecker()); const classNode = getDeclaration( program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', ts.isVariableDeclaration); @@ -830,7 +830,7 @@ describe('Esm5ReflectionHost', () => { }); it('should throw if the symbol is not a class', () => { - const program = makeProgram(FOO_FUNCTION_FILE); + const program = makeTestProgram(FOO_FUNCTION_FILE); const host = new Esm5ReflectionHost(false, program.getTypeChecker()); const functionNode = getDeclaration(program, FOO_FUNCTION_FILE.name, 'foo', ts.isFunctionDeclaration); @@ -843,7 +843,7 @@ describe('Esm5ReflectionHost', () => { // it('should return `null` if there is no constructor', () => { }); it('should return an array even if there are no decorators', () => { - const program = makeProgram(SIMPLE_CLASS_FILE); + const program = makeTestProgram(SIMPLE_CLASS_FILE); const host = new Esm5ReflectionHost(false, program.getTypeChecker()); const classNode = getDeclaration( program, SIMPLE_CLASS_FILE.name, 'NoDecoratorConstructorClass', ts.isVariableDeclaration); @@ -856,7 +856,7 @@ describe('Esm5ReflectionHost', () => { }); it('should return an empty array if there are no constructor parameters', () => { - const program = makeProgram(INVALID_CTOR_DECORATORS_FILE); + const program = makeTestProgram(INVALID_CTOR_DECORATORS_FILE); const host = new Esm5ReflectionHost(false, program.getTypeChecker()); const classNode = getDeclaration( program, INVALID_CTOR_DECORATORS_FILE.name, 'NoParameters', ts.isVariableDeclaration); @@ -869,7 +869,7 @@ describe('Esm5ReflectionHost', () => { // it('should ignore `ctorParameters` if it is an arrow function', () => { }); it('should ignore `ctorParameters` if it does not return an array literal', () => { - const program = makeProgram(INVALID_CTOR_DECORATORS_FILE); + const program = makeTestProgram(INVALID_CTOR_DECORATORS_FILE); const host = new Esm5ReflectionHost(false, program.getTypeChecker()); const classNode = getDeclaration( program, INVALID_CTOR_DECORATORS_FILE.name, 'NotArrayLiteral', ts.isVariableDeclaration); @@ -884,7 +884,7 @@ describe('Esm5ReflectionHost', () => { describe('(returned parameters `decorators`)', () => { it('should ignore param decorator elements that are not object literals', () => { - const program = makeProgram(INVALID_CTOR_DECORATORS_FILE); + const program = makeTestProgram(INVALID_CTOR_DECORATORS_FILE); const host = new Esm5ReflectionHost(false, program.getTypeChecker()); const classNode = getDeclaration( program, INVALID_CTOR_DECORATORS_FILE.name, 'NotObjectLiteral', @@ -903,7 +903,7 @@ describe('Esm5ReflectionHost', () => { }); it('should ignore param decorator elements that have no `type` property', () => { - const program = makeProgram(INVALID_CTOR_DECORATORS_FILE); + const program = makeTestProgram(INVALID_CTOR_DECORATORS_FILE); const host = new Esm5ReflectionHost(false, program.getTypeChecker()); const classNode = getDeclaration( program, INVALID_CTOR_DECORATORS_FILE.name, 'NoTypeProperty', ts.isVariableDeclaration); @@ -915,7 +915,7 @@ describe('Esm5ReflectionHost', () => { }); it('should ignore param decorator elements whose `type` value is not an identifier', () => { - const program = makeProgram(INVALID_CTOR_DECORATORS_FILE); + const program = makeTestProgram(INVALID_CTOR_DECORATORS_FILE); const host = new Esm5ReflectionHost(false, program.getTypeChecker()); const classNode = getDeclaration( program, INVALID_CTOR_DECORATORS_FILE.name, 'NotIdentifier', ts.isVariableDeclaration); @@ -931,7 +931,7 @@ describe('Esm5ReflectionHost', () => { const spy = spyOn(Esm5ReflectionHost.prototype, 'getImportOfIdentifier') .and.returnValue(mockImportInfo); - const program = makeProgram(SOME_DIRECTIVE_FILE); + const program = makeTestProgram(SOME_DIRECTIVE_FILE); const host = new Esm5ReflectionHost(false, program.getTypeChecker()); const classNode = getDeclaration( program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', ts.isVariableDeclaration); @@ -948,7 +948,7 @@ describe('Esm5ReflectionHost', () => { describe('(returned parameters `decorators.args`)', () => { it('should be an empty array if param decorator has no `args` property', () => { - const program = makeProgram(INVALID_CTOR_DECORATOR_ARGS_FILE); + const program = makeTestProgram(INVALID_CTOR_DECORATOR_ARGS_FILE); const host = new Esm5ReflectionHost(false, program.getTypeChecker()); const classNode = getDeclaration( program, INVALID_CTOR_DECORATOR_ARGS_FILE.name, 'NoArgsProperty', @@ -963,7 +963,7 @@ describe('Esm5ReflectionHost', () => { }); it('should be an empty array if param decorator\'s `args` has no property assignment', () => { - const program = makeProgram(INVALID_CTOR_DECORATOR_ARGS_FILE); + const program = makeTestProgram(INVALID_CTOR_DECORATOR_ARGS_FILE); const host = new Esm5ReflectionHost(false, program.getTypeChecker()); const classNode = getDeclaration( program, INVALID_CTOR_DECORATOR_ARGS_FILE.name, 'NoPropertyAssignment', @@ -977,7 +977,7 @@ describe('Esm5ReflectionHost', () => { }); it('should be an empty array if `args` property value is not an array literal', () => { - const program = makeProgram(INVALID_CTOR_DECORATOR_ARGS_FILE); + const program = makeTestProgram(INVALID_CTOR_DECORATOR_ARGS_FILE); const host = new Esm5ReflectionHost(false, program.getTypeChecker()); const classNode = getDeclaration( program, INVALID_CTOR_DECORATOR_ARGS_FILE.name, 'NotArrayLiteral', @@ -994,7 +994,7 @@ describe('Esm5ReflectionHost', () => { describe('getDefinitionOfFunction()', () => { it('should return an object describing the function declaration passed as an argument', () => { - const program = makeProgram(FUNCTION_BODY_FILE); + const program = makeTestProgram(FUNCTION_BODY_FILE); const host = new Esm5ReflectionHost(false, program.getTypeChecker()); const fooNode = @@ -1042,7 +1042,7 @@ describe('Esm5ReflectionHost', () => { describe('getImportOfIdentifier', () => { it('should find the import of an identifier', () => { - const program = makeProgram(...IMPORTS_FILES); + const program = makeTestProgram(...IMPORTS_FILES); const host = new Esm5ReflectionHost(false, program.getTypeChecker()); const variableNode = getDeclaration(program, IMPORTS_FILES[1].name, 'b', ts.isVariableDeclaration); @@ -1052,7 +1052,7 @@ describe('Esm5ReflectionHost', () => { }); it('should find the name by which the identifier was exported, not imported', () => { - const program = makeProgram(...IMPORTS_FILES); + const program = makeTestProgram(...IMPORTS_FILES); const host = new Esm5ReflectionHost(false, program.getTypeChecker()); const variableNode = getDeclaration(program, IMPORTS_FILES[1].name, 'c', ts.isVariableDeclaration); @@ -1062,7 +1062,7 @@ describe('Esm5ReflectionHost', () => { }); it('should return null if the identifier was not imported', () => { - const program = makeProgram(...IMPORTS_FILES); + const program = makeTestProgram(...IMPORTS_FILES); const host = new Esm5ReflectionHost(false, program.getTypeChecker()); const variableNode = getDeclaration(program, IMPORTS_FILES[1].name, 'd', ts.isVariableDeclaration); @@ -1074,7 +1074,7 @@ describe('Esm5ReflectionHost', () => { describe('getDeclarationOfIdentifier', () => { it('should return the declaration of a locally defined identifier', () => { - const program = makeProgram(SOME_DIRECTIVE_FILE); + const program = makeTestProgram(SOME_DIRECTIVE_FILE); const host = new Esm5ReflectionHost(false, program.getTypeChecker()); const classNode = getDeclaration( program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', ts.isVariableDeclaration); @@ -1090,7 +1090,7 @@ describe('Esm5ReflectionHost', () => { }); it('should return the declaration of an externally defined identifier', () => { - const program = makeProgram(SOME_DIRECTIVE_FILE); + const program = makeTestProgram(SOME_DIRECTIVE_FILE); const host = new Esm5ReflectionHost(false, program.getTypeChecker()); const classNode = getDeclaration( program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', ts.isVariableDeclaration); @@ -1110,7 +1110,7 @@ describe('Esm5ReflectionHost', () => { describe('getExportsOfModule()', () => { it('should return a map of all the exports from a given module', () => { - const program = makeProgram(...EXPORTS_FILES); + const program = makeTestProgram(...EXPORTS_FILES); const host = new Esm5ReflectionHost(false, program.getTypeChecker()); const file = program.getSourceFile(EXPORTS_FILES[1].name) !; const exportDeclarations = host.getExportsOfModule(file); @@ -1168,7 +1168,7 @@ describe('Esm5ReflectionHost', () => { }); it('should return the class symbol for an ES5 class (outer variable declaration)', () => { - const program = makeProgram(SIMPLE_CLASS_FILE); + const program = makeTestProgram(SIMPLE_CLASS_FILE); const host = new Esm5ReflectionHost(false, program.getTypeChecker()); const node = getDeclaration(program, SIMPLE_CLASS_FILE.name, 'EmptyClass', ts.isVariableDeclaration); @@ -1176,7 +1176,7 @@ describe('Esm5ReflectionHost', () => { }); it('should return the class symbol for an ES5 class (inner function declaration)', () => { - const program = makeProgram(SIMPLE_CLASS_FILE); + const program = makeTestProgram(SIMPLE_CLASS_FILE); const host = new Esm5ReflectionHost(false, program.getTypeChecker()); const outerNode = getDeclaration(program, SIMPLE_CLASS_FILE.name, 'EmptyClass', ts.isVariableDeclaration); @@ -1190,7 +1190,7 @@ describe('Esm5ReflectionHost', () => { it('should return the same class symbol (of the inner declaration) for outer and inner declarations', () => { - const program = makeProgram(SIMPLE_CLASS_FILE); + const program = makeTestProgram(SIMPLE_CLASS_FILE); const host = new Esm5ReflectionHost(false, program.getTypeChecker()); const outerNode = getDeclaration( program, SIMPLE_CLASS_FILE.name, 'EmptyClass', ts.isVariableDeclaration); @@ -1204,7 +1204,7 @@ describe('Esm5ReflectionHost', () => { }); it('should return undefined if node is not an ES5 class', () => { - const program = makeProgram(FOO_FUNCTION_FILE); + const program = makeTestProgram(FOO_FUNCTION_FILE); const host = new Esm5ReflectionHost(false, program.getTypeChecker()); const node = getDeclaration(program, FOO_FUNCTION_FILE.name, 'foo', ts.isFunctionDeclaration); expect(host.getClassSymbol(node)).toBeUndefined(); @@ -1254,7 +1254,7 @@ describe('Esm5ReflectionHost', () => { describe('findDecoratedClasses()', () => { it('should return an array of all decorated classes in the given source file', () => { - const program = makeProgram(...DECORATED_FILES); + const program = makeTestProgram(...DECORATED_FILES); const host = new Esm5ReflectionHost(false, program.getTypeChecker()); const primary = program.getSourceFile(DECORATED_FILES[0].name) !; diff --git a/packages/compiler-cli/src/ngcc/test/packages/dependency_host_spec.ts b/packages/compiler-cli/src/ngcc/test/packages/dependency_host_spec.ts index 1542cfea9ec6..fc19dd07e42b 100644 --- a/packages/compiler-cli/src/ngcc/test/packages/dependency_host_spec.ts +++ b/packages/compiler-cli/src/ngcc/test/packages/dependency_host_spec.ts @@ -222,29 +222,30 @@ describe('DependencyHost', () => { } }); - describe('hasImportOrReeportStatements', () => { + describe('hasImportOrReexportStatements', () => { it('should return true if there is an import statement', () => { - expect(host.hasImportOrReeportStatements('import {X} from "some/x";')).toBe(true); - expect(host.hasImportOrReeportStatements('import * as X from "some/x";')).toBe(true); + expect(host.hasImportOrReexportStatements('import {X} from "some/x";')).toBe(true); + expect(host.hasImportOrReexportStatements('import * as X from "some/x";')).toBe(true); expect( - host.hasImportOrReeportStatements('blah blah\n\n import {X} from "some/x";\nblah blah')) + host.hasImportOrReexportStatements('blah blah\n\n import {X} from "some/x";\nblah blah')) .toBe(true); - expect(host.hasImportOrReeportStatements('\t\timport {X} from "some/x";')).toBe(true); + expect(host.hasImportOrReexportStatements('\t\timport {X} from "some/x";')).toBe(true); }); it('should return true if there is a re-export statement', () => { - expect(host.hasImportOrReeportStatements('export {X} from "some/x";')).toBe(true); + expect(host.hasImportOrReexportStatements('export {X} from "some/x";')).toBe(true); expect( - host.hasImportOrReeportStatements('blah blah\n\n export {X} from "some/x";\nblah blah')) + host.hasImportOrReexportStatements('blah blah\n\n export {X} from "some/x";\nblah blah')) .toBe(true); - expect(host.hasImportOrReeportStatements('\t\texport {X} from "some/x";')).toBe(true); - expect(host.hasImportOrReeportStatements( + expect(host.hasImportOrReexportStatements('\t\texport {X} from "some/x";')).toBe(true); + expect(host.hasImportOrReexportStatements( 'blah blah\n\n export * from "@angular/core;\nblah blah')) .toBe(true); }); it('should return false if there is no import nor re-export statement', () => { - expect(host.hasImportOrReeportStatements('blah blah')).toBe(false); - expect(host.hasImportOrReeportStatements('export function moo() {}')).toBe(false); - expect(host.hasImportOrReeportStatements('Some text that happens to include the word import')) + expect(host.hasImportOrReexportStatements('blah blah')).toBe(false); + expect(host.hasImportOrReexportStatements('export function moo() {}')).toBe(false); + expect( + host.hasImportOrReexportStatements('Some text that happens to include the word import')) .toBe(false); }); }); diff --git a/packages/compiler-cli/src/ngcc/test/rendering/esm2015_renderer_spec.ts b/packages/compiler-cli/src/ngcc/test/rendering/esm2015_renderer_spec.ts index 7d51ce172eea..8e91618073c5 100644 --- a/packages/compiler-cli/src/ngcc/test/rendering/esm2015_renderer_spec.ts +++ b/packages/compiler-cli/src/ngcc/test/rendering/esm2015_renderer_spec.ts @@ -6,31 +6,35 @@ * found in the LICENSE file at https://angular.io/license */ import {dirname} from 'canonical-path'; -import * as ts from 'typescript'; - import MagicString from 'magic-string'; -import {makeProgram} from '../helpers/utils'; +import * as ts from 'typescript'; import {DecorationAnalyzer} from '../../src/analysis/decoration_analyzer'; +import {NgccReferencesRegistry} from '../../src/analysis/ngcc_references_registry'; import {SwitchMarkerAnalyzer} from '../../src/analysis/switch_marker_analyzer'; -import {createBundleInfo} from '../../src/packages/bundle'; import {Esm2015ReflectionHost} from '../../src/host/esm2015_host'; import {EsmRenderer} from '../../src/rendering/esm_renderer'; +import {makeTestEntryPointBundle} from '../helpers/utils'; -function setup(file: {name: string, contents: string}, transformDts: boolean = false) { +function setup(file: {name: string, contents: string}) { const dir = dirname(file.name); - const program = makeProgram(file); - const sourceFile = program.getSourceFile(file.name) !; - const host = new Esm2015ReflectionHost(false, program.getTypeChecker()); + const bundle = makeTestEntryPointBundle('esm2015', [file]) !; + const typeChecker = bundle.src.program.getTypeChecker(); + const host = new Esm2015ReflectionHost(false, typeChecker); + const referencesRegistry = new NgccReferencesRegistry(host); const decorationAnalyses = - new DecorationAnalyzer(program.getTypeChecker(), host, [''], false).analyzeProgram(program); - const switchMarkerAnalyses = new SwitchMarkerAnalyzer(host).analyzeProgram(program); - const bundle = createBundleInfo(false, null, null); - const renderer = new EsmRenderer(host, bundle, dir, dir, false); - return {host, program, sourceFile, renderer, decorationAnalyses, switchMarkerAnalyses}; + new DecorationAnalyzer(typeChecker, host, referencesRegistry, [''], false) + .analyzeProgram(bundle.src.program); + const switchMarkerAnalyses = new SwitchMarkerAnalyzer(host).analyzeProgram(bundle.src.program); + const renderer = new EsmRenderer(host, false, bundle, dir, dir); + return { + host, + program: bundle.src.program, + sourceFile: bundle.src.file, renderer, decorationAnalyses, switchMarkerAnalyses + }; } const PROGRAM = { - name: 'some/file.js', + name: '/some/file.js', contents: ` /* A copyright notice */ import {Directive} from '@angular/core'; @@ -65,7 +69,7 @@ function compileNgModuleFactory__POST_R3__(injector, options, moduleType) { }; const PROGRAM_DECORATE_HELPER = { - name: 'some/file.js', + name: '/some/file.js', contents: ` import * as tslib_1 from "tslib"; var D_1; @@ -117,6 +121,24 @@ import * as i1 from '@angular/common'; }); }); + describe('addExports', () => { + it('should insert the given exports at the end of the source file', () => { + const {renderer} = setup(PROGRAM); + const output = new MagicString(PROGRAM.contents); + renderer.addExports(output, PROGRAM.name.replace(/\.js$/, ''), [ + {from: '/some/a.js', identifier: 'ComponentA1'}, + {from: '/some/a.js', identifier: 'ComponentA2'}, + {from: '/some/foo/b.js', identifier: 'ComponentB'}, + {from: PROGRAM.name, identifier: 'TopLevelComponent'}, + ]); + expect(output.toString()).toContain(` +// Some other content +export {ComponentA1} from './a'; +export {ComponentA2} from './a'; +export {ComponentB} from './foo/b'; +export {TopLevelComponent};`); + }); + }); describe('addConstants', () => { it('should insert the given constants after imports in the source file', () => { diff --git a/packages/compiler-cli/src/ngcc/test/rendering/esm5_renderer_spec.ts b/packages/compiler-cli/src/ngcc/test/rendering/esm5_renderer_spec.ts index c04bbee6106a..24e8db34ac12 100644 --- a/packages/compiler-cli/src/ngcc/test/rendering/esm5_renderer_spec.ts +++ b/packages/compiler-cli/src/ngcc/test/rendering/esm5_renderer_spec.ts @@ -5,29 +5,36 @@ * 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 * as ts from 'typescript'; +import {dirname} from 'canonical-path'; import MagicString from 'magic-string'; -import {makeProgram, getDeclaration} from '../helpers/utils'; +import * as ts from 'typescript'; import {DecorationAnalyzer} from '../../src/analysis/decoration_analyzer'; +import {NgccReferencesRegistry} from '../../src/analysis/ngcc_references_registry'; import {SwitchMarkerAnalyzer} from '../../src/analysis/switch_marker_analyzer'; -import {createBundleInfo} from '../../src/packages/bundle'; import {Esm5ReflectionHost} from '../../src/host/esm5_host'; import {Esm5Renderer} from '../../src/rendering/esm5_renderer'; +import {makeTestEntryPointBundle, getDeclaration} from '../helpers/utils'; function setup(file: {name: string, contents: string}) { - const program = makeProgram(file); - const sourceFile = program.getSourceFile(file.name) !; - const host = new Esm5ReflectionHost(false, program.getTypeChecker()); + const dir = dirname(file.name); + const bundle = makeTestEntryPointBundle('esm5', [file]); + const typeChecker = bundle.src.program.getTypeChecker(); + const host = new Esm5ReflectionHost(false, typeChecker); + const referencesRegistry = new NgccReferencesRegistry(host); const decorationAnalyses = - new DecorationAnalyzer(program.getTypeChecker(), host, [''], false).analyzeProgram(program); - const switchMarkerAnalyses = new SwitchMarkerAnalyzer(host).analyzeProgram(program); - const bundle = createBundleInfo(false, null, null); - const renderer = new Esm5Renderer(host, bundle, '', '', false); - return {host, program, sourceFile, renderer, decorationAnalyses, switchMarkerAnalyses}; + new DecorationAnalyzer(typeChecker, host, referencesRegistry, [''], false) + .analyzeProgram(bundle.src.program); + const switchMarkerAnalyses = new SwitchMarkerAnalyzer(host).analyzeProgram(bundle.src.program); + const renderer = new Esm5Renderer(host, false, bundle, dir, dir); + return { + host, + program: bundle.src.program, + sourceFile: bundle.src.file, renderer, decorationAnalyses, switchMarkerAnalyses + }; } const PROGRAM = { - name: 'some/file.js', + name: '/some/file.js', contents: ` /* A copyright notice */ import {Directive} from '@angular/core'; @@ -86,7 +93,7 @@ export {A, B, C, NoIife, BadIife};` }; const PROGRAM_DECORATE_HELPER = { - name: 'some/file.js', + name: '/some/file.js', contents: ` import * as tslib_1 from "tslib"; /* A copyright notice */ @@ -151,6 +158,24 @@ import * as i1 from '@angular/common'; }); }); + describe('addExports', () => { + it('should insert the given exports at the end of the source file', () => { + const {renderer} = setup(PROGRAM); + const output = new MagicString(PROGRAM.contents); + renderer.addExports(output, PROGRAM.name.replace(/\.js$/, ''), [ + {from: '/some/a.js', identifier: 'ComponentA1'}, + {from: '/some/a.js', identifier: 'ComponentA2'}, + {from: '/some/foo/b.js', identifier: 'ComponentB'}, + {from: PROGRAM.name, identifier: 'TopLevelComponent'}, + ]); + expect(output.toString()).toContain(` +export {A, B, C, NoIife, BadIife}; +export {ComponentA1} from './a'; +export {ComponentA2} from './a'; +export {ComponentB} from './foo/b'; +export {TopLevelComponent};`); + }); + }); describe('addConstants', () => { it('should insert the given constants after imports in the source file', () => { diff --git a/packages/compiler-cli/src/ngcc/test/rendering/renderer_spec.ts b/packages/compiler-cli/src/ngcc/test/rendering/renderer_spec.ts index e63fa81f8931..e27c1f45146e 100644 --- a/packages/compiler-cli/src/ngcc/test/rendering/renderer_spec.ts +++ b/packages/compiler-cli/src/ngcc/test/rendering/renderer_spec.ts @@ -6,24 +6,31 @@ * found in the LICENSE file at https://angular.io/license */ import * as fs from 'fs'; -import * as ts from 'typescript'; - import MagicString from 'magic-string'; +import * as ts from 'typescript'; import {fromObject, generateMapFileComment} from 'convert-source-map'; -import {makeProgram} from '../helpers/utils'; import {CompiledClass, DecorationAnalyzer} from '../../src/analysis/decoration_analyzer'; +import {NgccReferencesRegistry} from '../../src/analysis/ngcc_references_registry'; +import {PrivateDeclarationsAnalyzer} from '../../src/analysis/private_declarations_analyzer'; import {SwitchMarkerAnalyzer} from '../../src/analysis/switch_marker_analyzer'; -import {BundleInfo, createBundleInfo} from '../../src/packages/bundle'; import {Esm2015ReflectionHost} from '../../src/host/esm2015_host'; import {Renderer} from '../../src/rendering/renderer'; +import {EntryPointBundle} from '../../src/packages/entry_point_bundle'; +import {makeTestEntryPointBundle} from '../helpers/utils'; class TestRenderer extends Renderer { - constructor(host: Esm2015ReflectionHost, bundle: BundleInfo) { - super(host, bundle, '/src', '/dist', false); + constructor(host: Esm2015ReflectionHost, isCore: boolean, bundle: EntryPointBundle) { + super(host, isCore, bundle, '/src', '/dist'); } addImports(output: MagicString, imports: {name: string, as: string}[]) { output.prepend('\n// ADD IMPORTS\n'); } + addExports(output: MagicString, baseEntryPointPath: string, exports: { + identifier: string, + from: string + }[]) { + output.prepend('\n// ADD EXPORTS\n'); + } addConstants(output: MagicString, constants: string, file: ts.SourceFile): void { output.prepend('\n// ADD CONSTANTS\n'); } @@ -39,23 +46,25 @@ class TestRenderer extends Renderer { } function createTestRenderer( - files: {name: string, contents: string}[], - options: {isCore?: boolean, rewriteCoreImportsTo?: string} = {}) { - const program = makeProgram(...files); - const rewriteCoreImportsTo = - options.rewriteCoreImportsTo ? program.getSourceFile(options.rewriteCoreImportsTo) ! : null; - const bundle = createBundleInfo(options.isCore || false, rewriteCoreImportsTo, null); - const host = new Esm2015ReflectionHost(bundle.isCore, program.getTypeChecker()); + packageName: string, files: {name: string, contents: string}[], + dtsFile?: {name: string, contents: string}) { + const isCore = packageName === '@angular/core'; + const bundle = makeTestEntryPointBundle('esm2015', files, dtsFile && [dtsFile]); + const typeChecker = bundle.src.program.getTypeChecker(); + const host = new Esm2015ReflectionHost(isCore, typeChecker, bundle.dts); + const referencesRegistry = new NgccReferencesRegistry(host); const decorationAnalyses = - new DecorationAnalyzer(program.getTypeChecker(), host, [''], bundle.isCore) - .analyzeProgram(program); - const switchMarkerAnalyses = new SwitchMarkerAnalyzer(host).analyzeProgram(program); - const renderer = new TestRenderer(host, bundle); + new DecorationAnalyzer(typeChecker, host, referencesRegistry, bundle.rootDirs, isCore) + .analyzeProgram(bundle.src.program); + const switchMarkerAnalyses = new SwitchMarkerAnalyzer(host).analyzeProgram(bundle.src.program); + const privateDeclarationsAnalyses = + new PrivateDeclarationsAnalyzer(host, referencesRegistry).analyzeProgram(bundle.src.program); + const renderer = new TestRenderer(host, isCore, bundle); spyOn(renderer, 'addImports').and.callThrough(); spyOn(renderer, 'addDefinitions').and.callThrough(); spyOn(renderer, 'removeDecorators').and.callThrough(); - return {renderer, program, decorationAnalyses, switchMarkerAnalyses}; + return {renderer, decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses}; } @@ -65,6 +74,10 @@ describe('Renderer', () => { contents: `import { Directive } from '@angular/core';\nexport class A {\n foo(x) {\n return x;\n }\n}\nA.decorators = [\n { type: Directive, args: [{ selector: '[a]' }] }\n];\n` }; + const INPUT_DTS_PROGRAM = { + name: '/typings/file.d.ts', + contents: `export declare class A {\nfoo(x: number): number;\n}\n` + }; const INPUT_PROGRAM_MAP = fromObject({ 'version': 3, @@ -78,7 +91,7 @@ describe('Renderer', () => { }); const RENDERED_CONTENTS = - `\n// REMOVE DECORATORS\n\n// ADD IMPORTS\n\n// ADD CONSTANTS\n\n// ADD DEFINITIONS\n` + + `\n// ADD EXPORTS\n\n// REMOVE DECORATORS\n\n// ADD IMPORTS\n\n// ADD CONSTANTS\n\n// ADD DEFINITIONS\n` + INPUT_PROGRAM.contents; const OUTPUT_PROGRAM_MAP = fromObject({ @@ -87,14 +100,14 @@ describe('Renderer', () => { 'sources': ['/src/file.js'], 'sourcesContent': [INPUT_PROGRAM.contents], 'names': [], - 'mappings': ';;;;;;;;AAAA;;;;;;;;;' + 'mappings': ';;;;;;;;;;AAAA;;;;;;;;;' }); const MERGED_OUTPUT_PROGRAM_MAP = fromObject({ 'version': 3, 'sources': ['/src/file.ts'], 'names': [], - 'mappings': ';;;;;;;;AAAA', + 'mappings': ';;;;;;;;;;AAAA', 'file': '/dist/file.js', 'sourcesContent': [INPUT_PROGRAM.contents] }); @@ -102,9 +115,10 @@ describe('Renderer', () => { describe('renderProgram()', () => { it('should render the modified contents; and a new map file, if the original provided no map file.', () => { - const {renderer, program, decorationAnalyses, switchMarkerAnalyses} = - createTestRenderer([INPUT_PROGRAM]); - const result = renderer.renderProgram(program, decorationAnalyses, switchMarkerAnalyses); + const {renderer, decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses} = + createTestRenderer('test-package', [INPUT_PROGRAM]); + const result = renderer.renderProgram( + decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses); expect(result[0].path).toEqual('/dist/file.js'); expect(result[0].contents) .toEqual(RENDERED_CONTENTS + '\n' + generateMapFileComment('/dist/file.js.map')); @@ -112,104 +126,118 @@ describe('Renderer', () => { expect(result[1].contents).toEqual(OUTPUT_PROGRAM_MAP.toJSON()); }); - it('should call addImports with the source code and info about the core Angular library.', - () => { - const {decorationAnalyses, program, renderer, switchMarkerAnalyses} = - createTestRenderer([INPUT_PROGRAM]); - renderer.renderProgram(program, decorationAnalyses, switchMarkerAnalyses); - const addImportsSpy = renderer.addImports as jasmine.Spy; - expect(addImportsSpy.calls.first().args[0].toString()).toEqual(RENDERED_CONTENTS); - expect(addImportsSpy.calls.first().args[1]).toEqual([ - {name: '@angular/core', as: 'ɵngcc0'} - ]); - }); + describe('calling abstract methods', () => { + it('should call addImports with the source code and info about the core Angular library.', + () => { + const {renderer, decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses} = + createTestRenderer('test-package', [INPUT_PROGRAM]); + const result = renderer.renderProgram( + decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses); + const addImportsSpy = renderer.addImports as jasmine.Spy; + expect(addImportsSpy.calls.first().args[0].toString()).toEqual(RENDERED_CONTENTS); + expect(addImportsSpy.calls.first().args[1]).toEqual([ + {name: '@angular/core', as: 'ɵngcc0'} + ]); + }); - it('should call addDefinitions with the source code, the analyzed class and the renderered definitions.', - () => { - const {decorationAnalyses, program, renderer, switchMarkerAnalyses} = - createTestRenderer([INPUT_PROGRAM]); - renderer.renderProgram(program, decorationAnalyses, switchMarkerAnalyses); - const addDefinitionsSpy = renderer.addDefinitions as jasmine.Spy; - expect(addDefinitionsSpy.calls.first().args[0].toString()).toEqual(RENDERED_CONTENTS); - expect(addDefinitionsSpy.calls.first().args[1]).toEqual(jasmine.objectContaining({ - name: 'A', - decorators: [jasmine.objectContaining({name: 'Directive'})], - })); - expect(addDefinitionsSpy.calls.first().args[2]) - .toEqual(`/*@__PURE__*/ ɵngcc0.ɵsetClassMetadata(A, [{ + it('should call addDefinitions with the source code, the analyzed class and the rendered definitions.', + () => { + const {renderer, decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses} = + createTestRenderer('test-package', [INPUT_PROGRAM]); + const result = renderer.renderProgram( + decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses); + const addDefinitionsSpy = renderer.addDefinitions as jasmine.Spy; + expect(addDefinitionsSpy.calls.first().args[0].toString()).toEqual(RENDERED_CONTENTS); + expect(addDefinitionsSpy.calls.first().args[1]).toEqual(jasmine.objectContaining({ + name: 'A', + decorators: [jasmine.objectContaining({name: 'Directive'})], + })); + expect(addDefinitionsSpy.calls.first().args[2]) + .toEqual(`/*@__PURE__*/ ɵngcc0.ɵsetClassMetadata(A, [{ type: Directive, args: [{ selector: '[a]' }] }], null, { foo: [] }); A.ngDirectiveDef = ɵngcc0.ɵdefineDirective({ type: A, selectors: [["", "a", ""]], factory: function A_Factory(t) { return new (t || A)(); } });`); - }); + }); - it('should call removeDecorators with the source code, a map of class decorators that have been analyzed', - () => { - const {decorationAnalyses, program, renderer, switchMarkerAnalyses} = - createTestRenderer([INPUT_PROGRAM]); - renderer.renderProgram(program, decorationAnalyses, switchMarkerAnalyses); - const removeDecoratorsSpy = renderer.removeDecorators as jasmine.Spy; - expect(removeDecoratorsSpy.calls.first().args[0].toString()).toEqual(RENDERED_CONTENTS); - - // Each map key is the TS node of the decorator container - // Each map value is an array of TS nodes that are the decorators to remove - const map = removeDecoratorsSpy.calls.first().args[1] as Map; - const keys = Array.from(map.keys()); - expect(keys.length).toEqual(1); - expect(keys[0].getText()) - .toEqual(`[\n { type: Directive, args: [{ selector: '[a]' }] }\n]`); - const values = Array.from(map.values()); - expect(values.length).toEqual(1); - expect(values[0].length).toEqual(1); - expect(values[0][0].getText()).toEqual(`{ type: Directive, args: [{ selector: '[a]' }] }`); - }); + it('should call removeDecorators with the source code, a map of class decorators that have been analyzed', + () => { + const {renderer, decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses} = + createTestRenderer('test-package', [INPUT_PROGRAM]); + const result = renderer.renderProgram( + decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses); + const removeDecoratorsSpy = renderer.removeDecorators as jasmine.Spy; + expect(removeDecoratorsSpy.calls.first().args[0].toString()).toEqual(RENDERED_CONTENTS); - it('should merge any inline source map from the original file and write the output as an inline source map', - () => { - const {decorationAnalyses, program, renderer, switchMarkerAnalyses} = createTestRenderer([{ - ...INPUT_PROGRAM, - contents: INPUT_PROGRAM.contents + '\n' + INPUT_PROGRAM_MAP.toComment() - }]); - const result = renderer.renderProgram(program, decorationAnalyses, switchMarkerAnalyses); - expect(result[0].path).toEqual('/dist/file.js'); - expect(result[0].contents) - .toEqual(RENDERED_CONTENTS + '\n' + MERGED_OUTPUT_PROGRAM_MAP.toComment()); - expect(result[1]).toBeUndefined(); - }); + // Each map key is the TS node of the decorator container + // Each map value is an array of TS nodes that are the decorators to remove + const map = removeDecoratorsSpy.calls.first().args[1] as Map; + const keys = Array.from(map.keys()); + expect(keys.length).toEqual(1); + expect(keys[0].getText()) + .toEqual(`[\n { type: Directive, args: [{ selector: '[a]' }] }\n]`); + const values = Array.from(map.values()); + expect(values.length).toEqual(1); + expect(values[0].length).toEqual(1); + expect(values[0][0].getText()) + .toEqual(`{ type: Directive, args: [{ selector: '[a]' }] }`); + }); + }); - it('should merge any external source map from the original file and write the output to an external source map', - () => { - // Mock out reading the map file from disk - spyOn(fs, 'readFileSync').and.returnValue(INPUT_PROGRAM_MAP.toJSON()); - const {decorationAnalyses, program, renderer, switchMarkerAnalyses} = createTestRenderer([{ - ...INPUT_PROGRAM, - contents: INPUT_PROGRAM.contents + '\n//# sourceMappingURL=file.js.map' - }]); - const result = renderer.renderProgram(program, decorationAnalyses, switchMarkerAnalyses); - expect(result[0].path).toEqual('/dist/file.js'); - expect(result[0].contents) - .toEqual(RENDERED_CONTENTS + '\n' + generateMapFileComment('/dist/file.js.map')); - expect(result[1].path).toEqual('/dist/file.js.map'); - expect(result[1].contents).toEqual(MERGED_OUTPUT_PROGRAM_MAP.toJSON()); - }); + describe('source map merging', () => { + it('should merge any inline source map from the original file and write the output as an inline source map', + () => { + const {decorationAnalyses, renderer, switchMarkerAnalyses, privateDeclarationsAnalyses} = + createTestRenderer( + 'test-package', [{ + ...INPUT_PROGRAM, + contents: INPUT_PROGRAM.contents + '\n' + INPUT_PROGRAM_MAP.toComment() + }]); + const result = renderer.renderProgram( + decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses); + expect(result[0].path).toEqual('/dist/file.js'); + expect(result[0].contents) + .toEqual(RENDERED_CONTENTS + '\n' + MERGED_OUTPUT_PROGRAM_MAP.toComment()); + expect(result[1]).toBeUndefined(); + }); - describe('@angular/core support', () => { + it('should merge any external source map from the original file and write the output to an external source map', + () => { + // Mock out reading the map file from disk + spyOn(fs, 'readFileSync').and.returnValue(INPUT_PROGRAM_MAP.toJSON()); + const {decorationAnalyses, renderer, switchMarkerAnalyses, privateDeclarationsAnalyses} = + createTestRenderer( + 'test-package', [{ + ...INPUT_PROGRAM, + contents: INPUT_PROGRAM.contents + '\n//# sourceMappingURL=file.js.map' + }]); + const result = renderer.renderProgram( + decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses); + expect(result[0].path).toEqual('/dist/file.js'); + expect(result[0].contents) + .toEqual(RENDERED_CONTENTS + '\n' + generateMapFileComment('/dist/file.js.map')); + expect(result[1].path).toEqual('/dist/file.js.map'); + expect(result[1].contents).toEqual(MERGED_OUTPUT_PROGRAM_MAP.toJSON()); + }); + }); + describe('@angular/core support', () => { it('should render relative imports in ESM bundles', () => { - const R3_SYMBOLS_FILE = { - name: '/src/r3_symbols.js', - contents: `export const NgModule = () => null;` - }; const CORE_FILE = { name: '/src/core.js', contents: `import { NgModule } from './ng_module';\nexport class MyModule {}\nMyModule.decorators = [\n { type: NgModule, args: [] }\n];\n` }; - - const {decorationAnalyses, program, renderer, switchMarkerAnalyses} = createTestRenderer( - [R3_SYMBOLS_FILE, CORE_FILE], - {isCore: true, rewriteCoreImportsTo: R3_SYMBOLS_FILE.name}); - renderer.renderProgram(program, decorationAnalyses, switchMarkerAnalyses); + const R3_SYMBOLS_FILE = { + // r3_symbols in the file name indicates that this is the path to rewrite core imports to + name: '/src/r3_symbols.js', + contents: `export const NgModule = () => null;` + }; + // The package name of `@angular/core` indicates that we are compiling the core library. + const {decorationAnalyses, renderer, switchMarkerAnalyses, privateDeclarationsAnalyses} = + createTestRenderer('@angular/core', [CORE_FILE, R3_SYMBOLS_FILE]); + renderer.renderProgram( + decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses); const addDefinitionsSpy = renderer.addDefinitions as jasmine.Spy; expect(addDefinitionsSpy.calls.first().args[2]) .toContain(`/*@__PURE__*/ ɵngcc0.setClassMetadata(`); @@ -224,16 +252,56 @@ A.ngDirectiveDef = ɵngcc0.ɵdefineDirective({ type: A, selectors: [["", "a", "" export class MyModule {}\nMyModule.decorators = [\n { type: NgModule, args: [] }\n];\n` }; - const {decorationAnalyses, program, renderer, switchMarkerAnalyses} = - createTestRenderer([CORE_FILE], {isCore: true}); - renderer.renderProgram(program, decorationAnalyses, switchMarkerAnalyses); + const {decorationAnalyses, renderer, switchMarkerAnalyses, privateDeclarationsAnalyses} = + createTestRenderer('@angular/core', [CORE_FILE]); + renderer.renderProgram( + decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses); const addDefinitionsSpy = renderer.addDefinitions as jasmine.Spy; expect(addDefinitionsSpy.calls.first().args[2]) .toContain(`/*@__PURE__*/ setClassMetadata(`); const addImportsSpy = renderer.addImports as jasmine.Spy; expect(addImportsSpy.calls.first().args[1]).toEqual([]); }); + }); + + describe('rendering typings', () => { + it('should render extract types into typings files', () => { + const {renderer, decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses} = + createTestRenderer('test-package', [INPUT_PROGRAM], INPUT_DTS_PROGRAM); + const result = renderer.renderProgram( + decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses); + + const typingsFile = result.find(f => f.path === '/typings/file.d.ts') !; + expect(typingsFile.contents) + .toContain( + 'foo(x: number): number;\n static ngDirectiveDef: ɵngcc0.ɵDirectiveDefWithMeta'); + }); + + it('should render imports into typings files', () => { + const {renderer, decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses} = + createTestRenderer('test-package', [INPUT_PROGRAM], INPUT_DTS_PROGRAM); + const result = renderer.renderProgram( + decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses); + const typingsFile = result.find(f => f.path === '/typings/file.d.ts') !; + expect(typingsFile.contents).toContain(`// ADD IMPORTS\nexport declare class A`); + }); + + it('should render exports into typings files', () => { + const {renderer, decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses} = + createTestRenderer('test-package', [INPUT_PROGRAM], INPUT_DTS_PROGRAM); + + // Add a mock export to trigger export rendering + privateDeclarationsAnalyses.push( + {identifier: 'ComponentB', from: '/src/file.js', dtsFrom: '/typings/b.d.ts'}); + + const result = renderer.renderProgram( + decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses); + + const typingsFile = result.find(f => f.path === '/typings/file.d.ts') !; + expect(typingsFile.contents) + .toContain(`// ADD EXPORTS\n\n// ADD IMPORTS\nexport declare class A`); + }); }); }); }); diff --git a/packages/compiler-cli/src/ngtsc/annotations/index.ts b/packages/compiler-cli/src/ngtsc/annotations/index.ts index 8902f606e895..ac736ce26450 100644 --- a/packages/compiler-cli/src/ngtsc/annotations/index.ts +++ b/packages/compiler-cli/src/ngtsc/annotations/index.ts @@ -15,4 +15,5 @@ export {DirectiveDecoratorHandler} from './src/directive'; export {InjectableDecoratorHandler} from './src/injectable'; export {NgModuleDecoratorHandler} from './src/ng_module'; export {PipeDecoratorHandler} from './src/pipe'; +export {NoopReferencesRegistry, ReferencesRegistry} from './src/references_registry'; export {CompilationScope, SelectorScopeRegistry} from './src/selector_scope'; diff --git a/packages/compiler-cli/src/ngtsc/annotations/src/ng_module.ts b/packages/compiler-cli/src/ngtsc/annotations/src/ng_module.ts index 23bd83bf31f0..5544ddeca827 100644 --- a/packages/compiler-cli/src/ngtsc/annotations/src/ng_module.ts +++ b/packages/compiler-cli/src/ngtsc/annotations/src/ng_module.ts @@ -15,6 +15,7 @@ import {Reference, ResolvedReference, ResolvedValue, reflectObjectLiteral, stati import {AnalysisOutput, CompileResult, DecoratorHandler} from '../../transform'; import {generateSetClassMetadataCall} from './metadata'; +import {ReferencesRegistry} from './references_registry'; import {SelectorScopeRegistry} from './selector_scope'; import {getConstructorDependencies, isAngularCore, toR3Reference, unwrapExpression} from './util'; @@ -32,7 +33,8 @@ export interface NgModuleAnalysis { export class NgModuleDecoratorHandler implements DecoratorHandler { constructor( private checker: ts.TypeChecker, private reflector: ReflectionHost, - private scopeRegistry: SelectorScopeRegistry, private isCore: boolean) {} + private scopeRegistry: SelectorScopeRegistry, private referencesRegistry: ReferencesRegistry, + private isCore: boolean) {} detect(node: ts.Declaration, decorators: Decorator[]|null): Decorator|undefined { if (!decorators) { @@ -72,6 +74,7 @@ export class NgModuleDecoratorHandler implements DecoratorHandler[] = []; if (ngModule.has('imports')) { @@ -80,6 +83,7 @@ export class NgModuleDecoratorHandler implements DecoratorHandler this._extractModuleFromModuleWithProvidersFn(ref.node)); imports = this.resolveTypeList(expr, importsMeta, 'imports'); + this.referencesRegistry.add(...imports); } let exports: Reference[] = []; if (ngModule.has('exports')) { @@ -88,12 +92,14 @@ export class NgModuleDecoratorHandler implements DecoratorHandler this._extractModuleFromModuleWithProvidersFn(ref.node)); exports = this.resolveTypeList(expr, exportsMeta, 'exports'); + this.referencesRegistry.add(...exports); } let bootstrap: Reference[] = []; if (ngModule.has('bootstrap')) { const expr = ngModule.get('bootstrap') !; const bootstrapMeta = staticallyResolve(expr, this.reflector, this.checker); bootstrap = this.resolveTypeList(expr, bootstrapMeta, 'bootstrap'); + this.referencesRegistry.add(...bootstrap); } // Register this module's information with the SelectorScopeRegistry. This ensures that during diff --git a/packages/compiler-cli/src/ngtsc/annotations/src/references_registry.ts b/packages/compiler-cli/src/ngtsc/annotations/src/references_registry.ts new file mode 100644 index 000000000000..a45ce6ad354e --- /dev/null +++ b/packages/compiler-cli/src/ngtsc/annotations/src/references_registry.ts @@ -0,0 +1,39 @@ +/** + * @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 * as ts from 'typescript'; +import {Declaration} from '../../host'; +import {Reference} from '../../metadata'; + +/** + * Implement this interface if you want DecoratorHandlers to register + * references that they find in their analysis of the code. + */ +export interface ReferencesRegistry { + /** + * Register one or more references in the registry. + * Only `ResolveReference` references are stored. Other types are ignored. + * @param references A collection of references to register. + */ + add(...references: Reference[]): void; + /** + * Create and return a mapping for the registered resolved references. + * @returns A map of reference identifiers to reference declarations. + */ + getDeclarationMap(): Map; +} + +/** + * This registry does nothing, since ngtsc does not currently need + * this functionality. + * The ngcc tool implements a working version for its purposes. + */ +export class NoopReferencesRegistry implements ReferencesRegistry { + add(...references: Reference[]): void {} + getDeclarationMap(): Map { return new Map(); } +} \ No newline at end of file diff --git a/packages/compiler-cli/src/ngtsc/program.ts b/packages/compiler-cli/src/ngtsc/program.ts index c925718dca2d..943566dcb444 100644 --- a/packages/compiler-cli/src/ngtsc/program.ts +++ b/packages/compiler-cli/src/ngtsc/program.ts @@ -13,7 +13,7 @@ import * as ts from 'typescript'; import * as api from '../transformers/api'; import {nocollapseHack} from '../transformers/nocollapse_hack'; -import {ComponentDecoratorHandler, DirectiveDecoratorHandler, InjectableDecoratorHandler, NgModuleDecoratorHandler, PipeDecoratorHandler, ResourceLoader, SelectorScopeRegistry} from './annotations'; +import {ComponentDecoratorHandler, DirectiveDecoratorHandler, InjectableDecoratorHandler, NgModuleDecoratorHandler, NoopReferencesRegistry, PipeDecoratorHandler, ResourceLoader, SelectorScopeRegistry} from './annotations'; import {BaseDefDecoratorHandler} from './annotations/src/base_def'; import {TypeScriptReflectionHost} from './metadata'; import {FileResourceLoader, HostResourceLoader} from './resource_loader'; @@ -214,6 +214,7 @@ export class NgtscProgram implements api.Program { private makeCompilation(): IvyCompilation { const checker = this.tsProgram.getTypeChecker(); const scopeRegistry = new SelectorScopeRegistry(checker, this.reflector); + const referencesRegistry = new NoopReferencesRegistry(); // Set up the IvyCompilation, which manages state for the Ivy transformer. const handlers = [ @@ -222,7 +223,8 @@ export class NgtscProgram implements api.Program { checker, this.reflector, scopeRegistry, this.isCore, this.resourceLoader, this.rootDirs), new DirectiveDecoratorHandler(checker, this.reflector, scopeRegistry, this.isCore), new InjectableDecoratorHandler(this.reflector, this.isCore), - new NgModuleDecoratorHandler(checker, this.reflector, scopeRegistry, this.isCore), + new NgModuleDecoratorHandler( + checker, this.reflector, scopeRegistry, referencesRegistry, this.isCore), new PipeDecoratorHandler(checker, this.reflector, scopeRegistry, this.isCore), ];