refactor(ivy): ngcc - implement abstract FileSystem (#29643)

This commit introduces a new interface, which abstracts access
to the underlying `FileSystem`. There is initially one concrete
implementation, `NodeJsFileSystem`, which is simply wrapping the
`fs` library of NodeJs.

Going forward, we can provide a `MockFileSystem` for test, which
should allow us to stop using `mock-fs` for most of the unit tests.
We could also implement a `CachedFileSystem` that may improve the
performance of ngcc.

PR Close #29643
This commit is contained in:
Pete Bacon Darwin 2019-04-28 20:47:57 +01:00 committed by Andrew Kushnir
parent 1fd2cc6340
commit 16d7dde2ad
36 changed files with 590 additions and 353 deletions

View File

@ -6,9 +6,7 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {ConstantPool} from '@angular/compiler'; import {ConstantPool} from '@angular/compiler';
import {NOOP_PERF_RECORDER} from '@angular/compiler-cli/src/ngtsc/perf'; import * as path from 'path';
import * as path from 'canonical-path';
import * as fs from 'fs';
import * as ts from 'typescript'; import * as ts from 'typescript';
import {BaseDefDecoratorHandler, ComponentDecoratorHandler, DirectiveDecoratorHandler, InjectableDecoratorHandler, NgModuleDecoratorHandler, PipeDecoratorHandler, ReferencesRegistry, ResourceLoader} from '../../../src/ngtsc/annotations'; import {BaseDefDecoratorHandler, ComponentDecoratorHandler, DirectiveDecoratorHandler, InjectableDecoratorHandler, NgModuleDecoratorHandler, PipeDecoratorHandler, ReferencesRegistry, ResourceLoader} from '../../../src/ngtsc/annotations';
@ -19,6 +17,7 @@ import {PartialEvaluator} from '../../../src/ngtsc/partial_evaluator';
import {AbsoluteFsPath, LogicalFileSystem} from '../../../src/ngtsc/path'; import {AbsoluteFsPath, LogicalFileSystem} from '../../../src/ngtsc/path';
import {LocalModuleScopeRegistry, MetadataDtsModuleScopeResolver} from '../../../src/ngtsc/scope'; import {LocalModuleScopeRegistry, MetadataDtsModuleScopeResolver} from '../../../src/ngtsc/scope';
import {CompileResult, DecoratorHandler, DetectResult, HandlerPrecedence} from '../../../src/ngtsc/transform'; import {CompileResult, DecoratorHandler, DetectResult, HandlerPrecedence} from '../../../src/ngtsc/transform';
import {FileSystem} from '../file_system/file_system';
import {DecoratedClass} from '../host/decorated_class'; import {DecoratedClass} from '../host/decorated_class';
import {NgccReflectionHost} from '../host/ngcc_host'; import {NgccReflectionHost} from '../host/ngcc_host';
import {isDefined} from '../utils'; import {isDefined} from '../utils';
@ -53,9 +52,10 @@ export interface MatchingHandler<A, M> {
* Simple class that resolves and loads files directly from the filesystem. * Simple class that resolves and loads files directly from the filesystem.
*/ */
class NgccResourceLoader implements ResourceLoader { class NgccResourceLoader implements ResourceLoader {
constructor(private fs: FileSystem) {}
canPreload = false; canPreload = false;
preload(): undefined|Promise<void> { throw new Error('Not implemented.'); } preload(): undefined|Promise<void> { throw new Error('Not implemented.'); }
load(url: string): string { return fs.readFileSync(url, 'utf8'); } load(url: string): string { return this.fs.readFile(AbsoluteFsPath.resolve(url)); }
resolve(url: string, containingFile: string): string { resolve(url: string, containingFile: string): string {
return path.resolve(path.dirname(containingFile), url); return path.resolve(path.dirname(containingFile), url);
} }
@ -65,7 +65,7 @@ class NgccResourceLoader implements ResourceLoader {
* This Analyzer will analyze the files that have decorated classes that need to be transformed. * This Analyzer will analyze the files that have decorated classes that need to be transformed.
*/ */
export class DecorationAnalyzer { export class DecorationAnalyzer {
resourceManager = new NgccResourceLoader(); resourceManager = new NgccResourceLoader(this.fs);
metaRegistry = new LocalMetadataRegistry(); metaRegistry = new LocalMetadataRegistry();
dtsMetaReader = new DtsMetadataReader(this.typeChecker, this.reflectionHost); dtsMetaReader = new DtsMetadataReader(this.typeChecker, this.reflectionHost);
fullMetaReader = new CompoundMetadataReader([this.metaRegistry, this.dtsMetaReader]); fullMetaReader = new CompoundMetadataReader([this.metaRegistry, this.dtsMetaReader]);
@ -73,8 +73,8 @@ export class DecorationAnalyzer {
new LocalIdentifierStrategy(), new LocalIdentifierStrategy(),
new AbsoluteModuleStrategy(this.program, this.typeChecker, this.options, this.host), new AbsoluteModuleStrategy(this.program, this.typeChecker, this.options, this.host),
// TODO(alxhub): there's no reason why ngcc needs the "logical file system" logic here, as ngcc // TODO(alxhub): there's no reason why ngcc needs the "logical file system" logic here, as ngcc
// projects only ever have one rootDir. Instead, ngcc should just switch its emitted imort based // projects only ever have one rootDir. Instead, ngcc should just switch its emitted import
// on whether a bestGuessOwningModule is present in the Reference. // based on whether a bestGuessOwningModule is present in the Reference.
new LogicalProjectStrategy(this.typeChecker, new LogicalFileSystem(this.rootDirs)), new LogicalProjectStrategy(this.typeChecker, new LogicalFileSystem(this.rootDirs)),
]); ]);
dtsModuleScopeResolver = dtsModuleScopeResolver =
@ -110,7 +110,7 @@ export class DecorationAnalyzer {
]; ];
constructor( constructor(
private program: ts.Program, private options: ts.CompilerOptions, private fs: FileSystem, private program: ts.Program, private options: ts.CompilerOptions,
private host: ts.CompilerHost, private typeChecker: ts.TypeChecker, private host: ts.CompilerHost, private typeChecker: ts.TypeChecker,
private reflectionHost: NgccReflectionHost, private referencesRegistry: ReferencesRegistry, private reflectionHost: NgccReflectionHost, private referencesRegistry: ReferencesRegistry,
private rootDirs: AbsoluteFsPath[], private isCore: boolean) {} private rootDirs: AbsoluteFsPath[], private isCore: boolean) {}

View File

@ -7,6 +7,7 @@
*/ */
import * as ts from 'typescript'; import * as ts from 'typescript';
import {AbsoluteFsPath} from '../../../src/ngtsc/path';
import {Declaration} from '../../../src/ngtsc/reflection'; import {Declaration} from '../../../src/ngtsc/reflection';
import {NgccReflectionHost} from '../host/ngcc_host'; import {NgccReflectionHost} from '../host/ngcc_host';
import {hasNameIdentifier, isDefined} from '../utils'; import {hasNameIdentifier, isDefined} from '../utils';
@ -14,8 +15,8 @@ import {NgccReferencesRegistry} from './ngcc_references_registry';
export interface ExportInfo { export interface ExportInfo {
identifier: string; identifier: string;
from: string; from: AbsoluteFsPath;
dtsFrom?: string|null; dtsFrom?: AbsoluteFsPath|null;
alias?: string|null; alias?: string|null;
} }
export type PrivateDeclarationsAnalyses = ExportInfo[]; export type PrivateDeclarationsAnalyses = ExportInfo[];
@ -93,11 +94,12 @@ export class PrivateDeclarationsAnalyzer {
}); });
return Array.from(privateDeclarations.keys()).map(id => { return Array.from(privateDeclarations.keys()).map(id => {
const from = id.getSourceFile().fileName; const from = AbsoluteFsPath.fromSourceFile(id.getSourceFile());
const declaration = privateDeclarations.get(id) !; const declaration = privateDeclarations.get(id) !;
const alias = exportAliasDeclarations.get(id) || null; const alias = exportAliasDeclarations.get(id) || null;
const dtsDeclaration = this.host.getDtsDeclaration(declaration.node); const dtsDeclaration = this.host.getDtsDeclaration(declaration.node);
const dtsFrom = dtsDeclaration && dtsDeclaration.getSourceFile().fileName; const dtsFrom =
dtsDeclaration && AbsoluteFsPath.fromSourceFile(dtsDeclaration.getSourceFile());
return {identifier: id.text, from, dtsFrom, alias}; return {identifier: id.text, from, dtsFrom, alias};
}); });

View File

@ -5,12 +5,10 @@
* Use of this source code is governed by an MIT-style license that can be * 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 * found in the LICENSE file at https://angular.io/license
*/ */
import * as fs from 'fs';
import * as ts from 'typescript'; import * as ts from 'typescript';
import {AbsoluteFsPath} from '../../../src/ngtsc/path'; import {AbsoluteFsPath} from '../../../src/ngtsc/path';
import {FileSystem} from '../file_system/file_system';
import {DependencyHost, DependencyInfo} from './dependency_host'; import {DependencyHost, DependencyInfo} from './dependency_host';
import {ModuleResolver, ResolvedDeepImport, ResolvedRelativeModule} from './module_resolver'; import {ModuleResolver, ResolvedDeepImport, ResolvedRelativeModule} from './module_resolver';
@ -19,7 +17,7 @@ import {ModuleResolver, ResolvedDeepImport, ResolvedRelativeModule} from './modu
* Helper functions for computing dependencies. * Helper functions for computing dependencies.
*/ */
export class EsmDependencyHost implements DependencyHost { export class EsmDependencyHost implements DependencyHost {
constructor(private moduleResolver: ModuleResolver) {} constructor(private fs: FileSystem, private moduleResolver: ModuleResolver) {}
/** /**
* Find all the dependencies for the entry-point at the given path. * Find all the dependencies for the entry-point at the given path.
@ -54,7 +52,7 @@ export class EsmDependencyHost implements DependencyHost {
private recursivelyFindDependencies( private recursivelyFindDependencies(
file: AbsoluteFsPath, dependencies: Set<AbsoluteFsPath>, missing: Set<string>, file: AbsoluteFsPath, dependencies: Set<AbsoluteFsPath>, missing: Set<string>,
deepImports: Set<string>, alreadySeen: Set<AbsoluteFsPath>): void { deepImports: Set<string>, alreadySeen: Set<AbsoluteFsPath>): void {
const fromContents = fs.readFileSync(file, 'utf8'); const fromContents = this.fs.readFile(file);
if (!this.hasImportOrReexportStatements(fromContents)) { if (!this.hasImportOrReexportStatements(fromContents)) {
return; return;
} }

View File

@ -6,12 +6,10 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import * as fs from 'fs';
import {AbsoluteFsPath} from '../../../src/ngtsc/path'; import {AbsoluteFsPath} from '../../../src/ngtsc/path';
import {FileSystem} from '../file_system/file_system';
import {PathMappings, isRelativePath} from '../utils'; import {PathMappings, isRelativePath} from '../utils';
/** /**
* This is a very cut-down implementation of the TypeScript module resolution strategy. * This is a very cut-down implementation of the TypeScript module resolution strategy.
* *
@ -28,7 +26,9 @@ import {PathMappings, isRelativePath} from '../utils';
export class ModuleResolver { export class ModuleResolver {
private pathMappings: ProcessedPathMapping[]; private pathMappings: ProcessedPathMapping[];
constructor(pathMappings?: PathMappings, private relativeExtensions = ['.js', '/index.js']) { constructor(private fs: FileSystem, pathMappings?: PathMappings, private relativeExtensions = [
'.js', '/index.js'
]) {
this.pathMappings = pathMappings ? this.processPathMappings(pathMappings) : []; this.pathMappings = pathMappings ? this.processPathMappings(pathMappings) : [];
} }
@ -139,11 +139,11 @@ export class ModuleResolver {
* to the `path` and checking if the file exists on disk. * to the `path` and checking if the file exists on disk.
* @returns An absolute path to the first matching existing file, or `null` if none exist. * @returns An absolute path to the first matching existing file, or `null` if none exist.
*/ */
private resolvePath(path: string, postFixes: string[]): AbsoluteFsPath|null { private resolvePath(path: AbsoluteFsPath, postFixes: string[]): AbsoluteFsPath|null {
for (const postFix of postFixes) { for (const postFix of postFixes) {
const testPath = path + postFix; const testPath = AbsoluteFsPath.fromUnchecked(path + postFix);
if (fs.existsSync(testPath)) { if (this.fs.exists(testPath)) {
return AbsoluteFsPath.from(testPath); return testPath;
} }
} }
return null; return null;
@ -155,7 +155,7 @@ export class ModuleResolver {
* This is achieved by checking for the existence of `${modulePath}/package.json`. * This is achieved by checking for the existence of `${modulePath}/package.json`.
*/ */
private isEntryPoint(modulePath: AbsoluteFsPath): boolean { private isEntryPoint(modulePath: AbsoluteFsPath): boolean {
return fs.existsSync(AbsoluteFsPath.join(modulePath, 'package.json')); return this.fs.exists(AbsoluteFsPath.join(modulePath, 'package.json'));
} }
/** /**
@ -227,7 +227,7 @@ export class ModuleResolver {
let folder = path; let folder = path;
while (folder !== '/') { while (folder !== '/') {
folder = AbsoluteFsPath.dirname(folder); folder = AbsoluteFsPath.dirname(folder);
if (fs.existsSync(AbsoluteFsPath.join(folder, 'package.json'))) { if (this.fs.exists(AbsoluteFsPath.join(folder, 'package.json'))) {
return folder; return folder;
} }
} }

View File

@ -0,0 +1,38 @@
/**
* @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 {AbsoluteFsPath, PathSegment} from '../../../src/ngtsc/path';
/**
* A basic interface to abstract the underlying file-system.
*
* This makes it easier to provide mock file-systems in unit tests,
* but also to create clever file-systems that have features such as caching.
*/
export interface FileSystem {
exists(path: AbsoluteFsPath): boolean;
readFile(path: AbsoluteFsPath): string;
writeFile(path: AbsoluteFsPath, data: string): void;
readdir(path: AbsoluteFsPath): PathSegment[];
lstat(path: AbsoluteFsPath): FileStats;
stat(path: AbsoluteFsPath): FileStats;
pwd(): AbsoluteFsPath;
copyFile(from: AbsoluteFsPath, to: AbsoluteFsPath): void;
moveFile(from: AbsoluteFsPath, to: AbsoluteFsPath): void;
ensureDir(path: AbsoluteFsPath): void;
}
/**
* Information about an object in the FileSystem.
* This is analogous to the `fs.Stats` class in Node.js.
*/
export interface FileStats {
isFile(): boolean;
isDirectory(): boolean;
isSymbolicLink(): boolean;
}

View File

@ -0,0 +1,30 @@
/**
* @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 fs from 'fs';
import {cp, mkdir, mv} from 'shelljs';
import {AbsoluteFsPath, PathSegment} from '../../../src/ngtsc/path';
import {FileSystem} from './file_system';
/**
* A wrapper around the Node.js file-system (i.e the `fs` package).
*/
export class NodeJSFileSystem implements FileSystem {
exists(path: AbsoluteFsPath): boolean { return fs.existsSync(path); }
readFile(path: AbsoluteFsPath): string { return fs.readFileSync(path, 'utf8'); }
writeFile(path: AbsoluteFsPath, data: string): void {
return fs.writeFileSync(path, data, 'utf8');
}
readdir(path: AbsoluteFsPath): PathSegment[] { return fs.readdirSync(path) as PathSegment[]; }
lstat(path: AbsoluteFsPath): fs.Stats { return fs.lstatSync(path); }
stat(path: AbsoluteFsPath): fs.Stats { return fs.statSync(path); }
pwd() { return AbsoluteFsPath.fromUnchecked(process.cwd()); }
copyFile(from: AbsoluteFsPath, to: AbsoluteFsPath): void { cp(from, to); }
moveFile(from: AbsoluteFsPath, to: AbsoluteFsPath): void { mv(from, to); }
ensureDir(path: AbsoluteFsPath): void { mkdir('-p', path); }
}

View File

@ -5,15 +5,13 @@
* Use of this source code is governed by an MIT-style license that can be * 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 * found in the LICENSE file at https://angular.io/license
*/ */
import {resolve} from 'canonical-path';
import {readFileSync} from 'fs';
import {AbsoluteFsPath} from '../../src/ngtsc/path'; import {AbsoluteFsPath} from '../../src/ngtsc/path';
import {DependencyResolver} from './dependencies/dependency_resolver'; import {DependencyResolver} from './dependencies/dependency_resolver';
import {EsmDependencyHost} from './dependencies/esm_dependency_host'; import {EsmDependencyHost} from './dependencies/esm_dependency_host';
import {ModuleResolver} from './dependencies/module_resolver'; import {ModuleResolver} from './dependencies/module_resolver';
import {FileSystem} from './file_system/file_system';
import {NodeJSFileSystem} from './file_system/node_js_file_system';
import {ConsoleLogger, LogLevel} from './logging/console_logger'; import {ConsoleLogger, LogLevel} from './logging/console_logger';
import {Logger} from './logging/logger'; import {Logger} from './logging/logger';
import {hasBeenProcessed, markAsProcessed} from './packages/build_marker'; import {hasBeenProcessed, markAsProcessed} from './packages/build_marker';
@ -26,8 +24,6 @@ import {FileWriter} from './writing/file_writer';
import {InPlaceFileWriter} from './writing/in_place_file_writer'; import {InPlaceFileWriter} from './writing/in_place_file_writer';
import {NewEntryPointFileWriter} from './writing/new_entry_point_file_writer'; import {NewEntryPointFileWriter} from './writing/new_entry_point_file_writer';
/** /**
* The options to configure the ngcc compiler. * The options to configure the ngcc compiler.
*/ */
@ -80,20 +76,20 @@ export function mainNgcc(
{basePath, targetEntryPointPath, propertiesToConsider = SUPPORTED_FORMAT_PROPERTIES, {basePath, targetEntryPointPath, propertiesToConsider = SUPPORTED_FORMAT_PROPERTIES,
compileAllFormats = true, createNewEntryPointFormats = false, compileAllFormats = true, createNewEntryPointFormats = false,
logger = new ConsoleLogger(LogLevel.info), pathMappings}: NgccOptions): void { logger = new ConsoleLogger(LogLevel.info), pathMappings}: NgccOptions): void {
const transformer = new Transformer(logger); const fs = new NodeJSFileSystem();
const moduleResolver = new ModuleResolver(pathMappings); const transformer = new Transformer(fs, logger);
const host = new EsmDependencyHost(moduleResolver); const moduleResolver = new ModuleResolver(fs, pathMappings);
const host = new EsmDependencyHost(fs, moduleResolver);
const resolver = new DependencyResolver(logger, host); const resolver = new DependencyResolver(logger, host);
const finder = new EntryPointFinder(logger, resolver); const finder = new EntryPointFinder(fs, logger, resolver);
const fileWriter = getFileWriter(createNewEntryPointFormats); const fileWriter = getFileWriter(fs, createNewEntryPointFormats);
const absoluteTargetEntryPointPath = targetEntryPointPath ? const absoluteTargetEntryPointPath =
AbsoluteFsPath.from(resolve(basePath, targetEntryPointPath)) : targetEntryPointPath ? AbsoluteFsPath.resolve(basePath, targetEntryPointPath) : undefined;
undefined;
if (absoluteTargetEntryPointPath && if (absoluteTargetEntryPointPath &&
hasProcessedTargetEntryPoint( hasProcessedTargetEntryPoint(
absoluteTargetEntryPointPath, propertiesToConsider, compileAllFormats)) { fs, absoluteTargetEntryPointPath, propertiesToConsider, compileAllFormats)) {
logger.info('The target entry-point has already been processed'); logger.info('The target entry-point has already been processed');
return; return;
} }
@ -102,7 +98,7 @@ export function mainNgcc(
AbsoluteFsPath.from(basePath), absoluteTargetEntryPointPath, pathMappings); AbsoluteFsPath.from(basePath), absoluteTargetEntryPointPath, pathMappings);
if (absoluteTargetEntryPointPath && entryPoints.length === 0) { if (absoluteTargetEntryPointPath && entryPoints.length === 0) {
markNonAngularPackageAsProcessed(absoluteTargetEntryPointPath, propertiesToConsider); markNonAngularPackageAsProcessed(fs, absoluteTargetEntryPointPath, propertiesToConsider);
return; return;
} }
@ -138,8 +134,8 @@ export function mainNgcc(
// the property as processed even if its underlying format has been built already. // the property as processed even if its underlying format has been built already.
if (!compiledFormats.has(formatPath) && (compileAllFormats || isFirstFormat)) { if (!compiledFormats.has(formatPath) && (compileAllFormats || isFirstFormat)) {
const bundle = makeEntryPointBundle( const bundle = makeEntryPointBundle(
entryPoint.path, formatPath, entryPoint.typings, isCore, property, format, processDts, fs, entryPoint.path, formatPath, entryPoint.typings, isCore, property, format,
pathMappings); processDts, pathMappings);
if (bundle) { if (bundle) {
logger.info(`Compiling ${entryPoint.name} : ${property} as ${format}`); logger.info(`Compiling ${entryPoint.name} : ${property} as ${format}`);
const transformedFiles = transformer.transform(bundle); const transformedFiles = transformer.transform(bundle);
@ -156,9 +152,9 @@ export function mainNgcc(
// Either this format was just compiled or its underlying format was compiled because of a // Either this format was just compiled or its underlying format was compiled because of a
// previous property. // previous property.
if (compiledFormats.has(formatPath)) { if (compiledFormats.has(formatPath)) {
markAsProcessed(entryPointPackageJson, entryPointPackageJsonPath, property); markAsProcessed(fs, entryPointPackageJson, entryPointPackageJsonPath, property);
if (processDts) { if (processDts) {
markAsProcessed(entryPointPackageJson, entryPointPackageJsonPath, 'typings'); markAsProcessed(fs, entryPointPackageJson, entryPointPackageJsonPath, 'typings');
} }
} }
} }
@ -170,14 +166,15 @@ export function mainNgcc(
}); });
} }
function getFileWriter(createNewEntryPointFormats: boolean): FileWriter { function getFileWriter(fs: FileSystem, createNewEntryPointFormats: boolean): FileWriter {
return createNewEntryPointFormats ? new NewEntryPointFileWriter() : new InPlaceFileWriter(); return createNewEntryPointFormats ? new NewEntryPointFileWriter(fs) : new InPlaceFileWriter(fs);
} }
function hasProcessedTargetEntryPoint( function hasProcessedTargetEntryPoint(
targetPath: AbsoluteFsPath, propertiesToConsider: string[], compileAllFormats: boolean) { fs: FileSystem, targetPath: AbsoluteFsPath, propertiesToConsider: string[],
const packageJsonPath = AbsoluteFsPath.from(resolve(targetPath, 'package.json')); compileAllFormats: boolean) {
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8')); const packageJsonPath = AbsoluteFsPath.resolve(targetPath, 'package.json');
const packageJson = JSON.parse(fs.readFile(packageJsonPath));
for (const property of propertiesToConsider) { for (const property of propertiesToConsider) {
if (packageJson[property]) { if (packageJson[property]) {
@ -205,11 +202,12 @@ function hasProcessedTargetEntryPoint(
* So mark all formats in this entry-point as processed so that clients of ngcc can avoid * So mark all formats in this entry-point as processed so that clients of ngcc can avoid
* triggering ngcc for this entry-point in the future. * triggering ngcc for this entry-point in the future.
*/ */
function markNonAngularPackageAsProcessed(path: AbsoluteFsPath, propertiesToConsider: string[]) { function markNonAngularPackageAsProcessed(
const packageJsonPath = AbsoluteFsPath.from(resolve(path, 'package.json')); fs: FileSystem, path: AbsoluteFsPath, propertiesToConsider: string[]) {
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8')); const packageJsonPath = AbsoluteFsPath.resolve(path, 'package.json');
const packageJson = JSON.parse(fs.readFile(packageJsonPath));
propertiesToConsider.forEach(formatProperty => { propertiesToConsider.forEach(formatProperty => {
if (packageJson[formatProperty]) if (packageJson[formatProperty])
markAsProcessed(packageJson, packageJsonPath, formatProperty as EntryPointJsonProperty); markAsProcessed(fs, packageJson, packageJsonPath, formatProperty as EntryPointJsonProperty);
}); });
} }

View File

@ -6,10 +6,8 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {writeFileSync} from 'fs';
import {AbsoluteFsPath} from '../../../src/ngtsc/path'; import {AbsoluteFsPath} from '../../../src/ngtsc/path';
import {FileSystem} from '../file_system/file_system';
import {EntryPointJsonProperty, EntryPointPackageJson} from './entry_point'; import {EntryPointJsonProperty, EntryPointPackageJson} from './entry_point';
export const NGCC_VERSION = '0.0.0-PLACEHOLDER'; export const NGCC_VERSION = '0.0.0-PLACEHOLDER';
@ -49,9 +47,9 @@ export function hasBeenProcessed(
* @param format the property in the package.json of the format for which we are writing the marker. * @param format the property in the package.json of the format for which we are writing the marker.
*/ */
export function markAsProcessed( export function markAsProcessed(
packageJson: EntryPointPackageJson, packageJsonPath: AbsoluteFsPath, fs: FileSystem, packageJson: EntryPointPackageJson, packageJsonPath: AbsoluteFsPath,
format: EntryPointJsonProperty) { format: EntryPointJsonProperty) {
if (!packageJson.__processed_by_ivy_ngcc__) packageJson.__processed_by_ivy_ngcc__ = {}; if (!packageJson.__processed_by_ivy_ngcc__) packageJson.__processed_by_ivy_ngcc__ = {};
packageJson.__processed_by_ivy_ngcc__[format] = NGCC_VERSION; packageJson.__processed_by_ivy_ngcc__[format] = NGCC_VERSION;
writeFileSync(packageJsonPath, JSON.stringify(packageJson), 'utf8'); fs.writeFile(packageJsonPath, JSON.stringify(packageJson));
} }

View File

@ -5,10 +5,11 @@
* Use of this source code is governed by an MIT-style license that can be * 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 * 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'; import * as ts from 'typescript';
import {AbsoluteFsPath} from '../../../src/ngtsc/path';
import {FileSystem} from '../file_system/file_system';
/** /**
* An entry point bundle contains one or two programs, e.g. `src` and `dts`, * An entry point bundle contains one or two programs, e.g. `src` and `dts`,
* that are compiled via TypeScript. * that are compiled via TypeScript.
@ -21,9 +22,9 @@ export interface BundleProgram {
program: ts.Program; program: ts.Program;
options: ts.CompilerOptions; options: ts.CompilerOptions;
host: ts.CompilerHost; host: ts.CompilerHost;
path: string; path: AbsoluteFsPath;
file: ts.SourceFile; file: ts.SourceFile;
r3SymbolsPath: string|null; r3SymbolsPath: AbsoluteFsPath|null;
r3SymbolsFile: ts.SourceFile|null; r3SymbolsFile: ts.SourceFile|null;
} }
@ -31,9 +32,10 @@ export interface BundleProgram {
* Create a bundle program. * Create a bundle program.
*/ */
export function makeBundleProgram( export function makeBundleProgram(
isCore: boolean, path: string, r3FileName: string, options: ts.CompilerOptions, fs: FileSystem, isCore: boolean, path: AbsoluteFsPath, r3FileName: string,
host: ts.CompilerHost): BundleProgram { options: ts.CompilerOptions, host: ts.CompilerHost): BundleProgram {
const r3SymbolsPath = isCore ? findR3SymbolsPath(dirname(path), r3FileName) : null; const r3SymbolsPath =
isCore ? findR3SymbolsPath(fs, AbsoluteFsPath.dirname(path), r3FileName) : null;
const rootPaths = r3SymbolsPath ? [path, r3SymbolsPath] : [path]; const rootPaths = r3SymbolsPath ? [path, r3SymbolsPath] : [path];
const program = ts.createProgram(rootPaths, options, host); const program = ts.createProgram(rootPaths, options, host);
const file = program.getSourceFile(path) !; const file = program.getSourceFile(path) !;
@ -45,26 +47,28 @@ export function makeBundleProgram(
/** /**
* Search the given directory hierarchy to find the path to the `r3_symbols` file. * Search the given directory hierarchy to find the path to the `r3_symbols` file.
*/ */
export function findR3SymbolsPath(directory: string, filename: string): string|null { export function findR3SymbolsPath(
const r3SymbolsFilePath = resolve(directory, filename); fs: FileSystem, directory: AbsoluteFsPath, filename: string): AbsoluteFsPath|null {
if (existsSync(r3SymbolsFilePath)) { const r3SymbolsFilePath = AbsoluteFsPath.resolve(directory, filename);
if (fs.exists(r3SymbolsFilePath)) {
return r3SymbolsFilePath; return r3SymbolsFilePath;
} }
const subDirectories = const subDirectories =
readdirSync(directory) fs.readdir(directory)
// Not interested in hidden files // Not interested in hidden files
.filter(p => !p.startsWith('.')) .filter(p => !p.startsWith('.'))
// Ignore node_modules // Ignore node_modules
.filter(p => p !== 'node_modules') .filter(p => p !== 'node_modules')
// Only interested in directories (and only those that are not symlinks) // Only interested in directories (and only those that are not symlinks)
.filter(p => { .filter(p => {
const stat = lstatSync(resolve(directory, p)); const stat = fs.lstat(AbsoluteFsPath.resolve(directory, p));
return stat.isDirectory() && !stat.isSymbolicLink(); return stat.isDirectory() && !stat.isSymbolicLink();
}); });
for (const subDirectory of subDirectories) { for (const subDirectory of subDirectories) {
const r3SymbolsFilePath = findR3SymbolsPath(resolve(directory, subDirectory, ), filename); const r3SymbolsFilePath =
findR3SymbolsPath(fs, AbsoluteFsPath.resolve(directory, subDirectory), filename);
if (r3SymbolsFilePath) { if (r3SymbolsFilePath) {
return r3SymbolsFilePath; return r3SymbolsFilePath;
} }

View File

@ -5,14 +5,10 @@
* Use of this source code is governed by an MIT-style license that can be * 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 * found in the LICENSE file at https://angular.io/license
*/ */
import * as path from 'canonical-path';
import * as fs from 'fs';
import {AbsoluteFsPath} from '../../../src/ngtsc/path'; import {AbsoluteFsPath} from '../../../src/ngtsc/path';
import {FileSystem} from '../file_system/file_system';
import {Logger} from '../logging/logger'; import {Logger} from '../logging/logger';
/** /**
* The possible values for the format of an entry-point. * The possible values for the format of an entry-point.
*/ */
@ -70,13 +66,14 @@ export const SUPPORTED_FORMAT_PROPERTIES: EntryPointJsonProperty[] =
* @returns An entry-point if it is valid, `null` otherwise. * @returns An entry-point if it is valid, `null` otherwise.
*/ */
export function getEntryPointInfo( export function getEntryPointInfo(
logger: Logger, packagePath: AbsoluteFsPath, entryPointPath: AbsoluteFsPath): EntryPoint|null { fs: FileSystem, logger: Logger, packagePath: AbsoluteFsPath,
const packageJsonPath = path.resolve(entryPointPath, 'package.json'); entryPointPath: AbsoluteFsPath): EntryPoint|null {
if (!fs.existsSync(packageJsonPath)) { const packageJsonPath = AbsoluteFsPath.resolve(entryPointPath, 'package.json');
if (!fs.exists(packageJsonPath)) {
return null; return null;
} }
const entryPointPackageJson = loadEntryPointPackage(logger, packageJsonPath); const entryPointPackageJson = loadEntryPointPackage(fs, logger, packageJsonPath);
if (!entryPointPackageJson) { if (!entryPointPackageJson) {
return null; return null;
} }
@ -90,15 +87,15 @@ export function getEntryPointInfo(
// Also there must exist a `metadata.json` file next to the typings entry-point. // Also there must exist a `metadata.json` file next to the typings entry-point.
const metadataPath = const metadataPath =
path.resolve(entryPointPath, typings.replace(/\.d\.ts$/, '') + '.metadata.json'); AbsoluteFsPath.resolve(entryPointPath, typings.replace(/\.d\.ts$/, '') + '.metadata.json');
const entryPointInfo: EntryPoint = { const entryPointInfo: EntryPoint = {
name: entryPointPackageJson.name, name: entryPointPackageJson.name,
packageJson: entryPointPackageJson, packageJson: entryPointPackageJson,
package: packagePath, package: packagePath,
path: entryPointPath, path: entryPointPath,
typings: AbsoluteFsPath.from(path.resolve(entryPointPath, typings)), typings: AbsoluteFsPath.resolve(entryPointPath, typings),
compiledByAngular: fs.existsSync(metadataPath), compiledByAngular: fs.exists(metadataPath),
}; };
return entryPointInfo; return entryPointInfo;
@ -136,10 +133,10 @@ export function getEntryPointFormat(property: string): EntryPointFormat|undefine
* @param packageJsonPath the absolute path to the package.json file. * @param packageJsonPath the absolute path to the package.json file.
* @returns JSON from the package.json file if it is valid, `null` otherwise. * @returns JSON from the package.json file if it is valid, `null` otherwise.
*/ */
function loadEntryPointPackage(logger: Logger, packageJsonPath: string): EntryPointPackageJson| function loadEntryPointPackage(
null { fs: FileSystem, logger: Logger, packageJsonPath: AbsoluteFsPath): EntryPointPackageJson|null {
try { try {
return JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')); return JSON.parse(fs.readFile(packageJsonPath));
} catch (e) { } catch (e) {
// We may have run into a package.json with unexpected symbols // We may have run into a package.json with unexpected symbols
logger.warn(`Failed to read entry point info from ${packageJsonPath} with error ${e}.`); logger.warn(`Failed to read entry point info from ${packageJsonPath} with error ${e}.`);

View File

@ -5,14 +5,14 @@
* Use of this source code is governed by an MIT-style license that can be * 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 * found in the LICENSE file at https://angular.io/license
*/ */
import {resolve} from 'canonical-path';
import * as ts from 'typescript'; import * as ts from 'typescript';
import {AbsoluteFsPath} from '../../../src/ngtsc/path'; import {AbsoluteFsPath} from '../../../src/ngtsc/path';
import {FileSystem} from '../file_system/file_system';
import {PathMappings} from '../utils'; import {PathMappings} from '../utils';
import {BundleProgram, makeBundleProgram} from './bundle_program'; import {BundleProgram, makeBundleProgram} from './bundle_program';
import {EntryPointFormat, EntryPointJsonProperty} from './entry_point'; import {EntryPointFormat, EntryPointJsonProperty} from './entry_point';
import {NgccCompilerHost} from './ngcc_compiler_host';
/** /**
* A bundle of files and paths (and TS programs) that correspond to a particular * A bundle of files and paths (and TS programs) that correspond to a particular
@ -38,25 +38,27 @@ export interface EntryPointBundle {
* @param transformDts Whether to transform the typings along with this bundle. * @param transformDts Whether to transform the typings along with this bundle.
*/ */
export function makeEntryPointBundle( export function makeEntryPointBundle(
entryPointPath: string, formatPath: string, typingsPath: string, isCore: boolean, fs: FileSystem, entryPointPath: string, formatPath: string, typingsPath: string,
formatProperty: EntryPointJsonProperty, format: EntryPointFormat, transformDts: boolean, isCore: boolean, formatProperty: EntryPointJsonProperty, format: EntryPointFormat,
pathMappings?: PathMappings): EntryPointBundle|null { transformDts: boolean, pathMappings?: PathMappings): EntryPointBundle|null {
// Create the TS program and necessary helpers. // Create the TS program and necessary helpers.
const options: ts.CompilerOptions = { const options: ts.CompilerOptions = {
allowJs: true, allowJs: true,
maxNodeModuleJsDepth: Infinity, maxNodeModuleJsDepth: Infinity,
noLib: true,
rootDir: entryPointPath, ...pathMappings rootDir: entryPointPath, ...pathMappings
}; };
const host = ts.createCompilerHost(options); const host = new NgccCompilerHost(fs, options);
const rootDirs = [AbsoluteFsPath.from(entryPointPath)]; const rootDirs = [AbsoluteFsPath.from(entryPointPath)];
// Create the bundle programs, as necessary. // Create the bundle programs, as necessary.
const src = makeBundleProgram( const src = makeBundleProgram(
isCore, resolve(entryPointPath, formatPath), 'r3_symbols.js', options, host); fs, isCore, AbsoluteFsPath.resolve(entryPointPath, formatPath), 'r3_symbols.js', options,
const dts = transformDts ? host);
makeBundleProgram( const dts = transformDts ? makeBundleProgram(
isCore, resolve(entryPointPath, typingsPath), 'r3_symbols.d.ts', options, host) : fs, isCore, AbsoluteFsPath.resolve(entryPointPath, typingsPath),
null; 'r3_symbols.d.ts', options, host) :
null;
const isFlatCore = isCore && src.r3SymbolsFile === null; const isFlatCore = isCore && src.r3SymbolsFile === null;
return {format, formatProperty, rootDirs, isCore, isFlatCore, src, dts}; return {format, formatProperty, rootDirs, isCore, isFlatCore, src, dts};

View File

@ -5,20 +5,16 @@
* Use of this source code is governed by an MIT-style license that can be * 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 * found in the LICENSE file at https://angular.io/license
*/ */
import * as path from 'canonical-path';
import * as fs from 'fs';
import {join, resolve} from 'path';
import {AbsoluteFsPath} from '../../../src/ngtsc/path'; import {AbsoluteFsPath} from '../../../src/ngtsc/path';
import {DependencyResolver, SortedEntryPointsInfo} from '../dependencies/dependency_resolver'; import {DependencyResolver, SortedEntryPointsInfo} from '../dependencies/dependency_resolver';
import {FileSystem} from '../file_system/file_system';
import {Logger} from '../logging/logger'; import {Logger} from '../logging/logger';
import {PathMappings} from '../utils'; import {PathMappings} from '../utils';
import {EntryPoint, getEntryPointInfo} from './entry_point'; import {EntryPoint, getEntryPointInfo} from './entry_point';
export class EntryPointFinder { export class EntryPointFinder {
constructor(private logger: Logger, private resolver: DependencyResolver) {} constructor(
private fs: FileSystem, private logger: Logger, private resolver: DependencyResolver) {}
/** /**
* Search the given directory, and sub-directories, for Angular package entry points. * Search the given directory, and sub-directories, for Angular package entry points.
* @param sourceDirectory An absolute path to the directory to search for entry points. * @param sourceDirectory An absolute path to the directory to search for entry points.
@ -59,9 +55,9 @@ export class EntryPointFinder {
AbsoluteFsPath[] { AbsoluteFsPath[] {
const basePaths = [sourceDirectory]; const basePaths = [sourceDirectory];
if (pathMappings) { if (pathMappings) {
const baseUrl = AbsoluteFsPath.from(resolve(pathMappings.baseUrl)); const baseUrl = AbsoluteFsPath.resolve(pathMappings.baseUrl);
values(pathMappings.paths).forEach(paths => paths.forEach(path => { values(pathMappings.paths).forEach(paths => paths.forEach(path => {
basePaths.push(AbsoluteFsPath.fromUnchecked(join(baseUrl, extractPathPrefix(path)))); basePaths.push(AbsoluteFsPath.join(baseUrl, extractPathPrefix(path)));
})); }));
} }
basePaths.sort(); // Get the paths in order with the shorter ones first. basePaths.sort(); // Get the paths in order with the shorter ones first.
@ -75,29 +71,29 @@ export class EntryPointFinder {
*/ */
private walkDirectoryForEntryPoints(sourceDirectory: AbsoluteFsPath): EntryPoint[] { private walkDirectoryForEntryPoints(sourceDirectory: AbsoluteFsPath): EntryPoint[] {
const entryPoints: EntryPoint[] = []; const entryPoints: EntryPoint[] = [];
fs.readdirSync(sourceDirectory) this.fs
.readdir(sourceDirectory)
// Not interested in hidden files // Not interested in hidden files
.filter(p => !p.startsWith('.')) .filter(p => !p.startsWith('.'))
// Ignore node_modules // Ignore node_modules
.filter(p => p !== 'node_modules') .filter(p => p !== 'node_modules')
// Only interested in directories (and only those that are not symlinks) // Only interested in directories (and only those that are not symlinks)
.filter(p => { .filter(p => {
const stat = fs.lstatSync(path.resolve(sourceDirectory, p)); const stat = this.fs.lstat(AbsoluteFsPath.resolve(sourceDirectory, p));
return stat.isDirectory() && !stat.isSymbolicLink(); return stat.isDirectory() && !stat.isSymbolicLink();
}) })
.forEach(p => { .forEach(p => {
// Either the directory is a potential package or a namespace containing packages (e.g // Either the directory is a potential package or a namespace containing packages (e.g
// `@angular`). // `@angular`).
const packagePath = AbsoluteFsPath.from(path.join(sourceDirectory, p)); const packagePath = AbsoluteFsPath.join(sourceDirectory, p);
if (p.startsWith('@')) { if (p.startsWith('@')) {
entryPoints.push(...this.walkDirectoryForEntryPoints(packagePath)); entryPoints.push(...this.walkDirectoryForEntryPoints(packagePath));
} else { } else {
entryPoints.push(...this.getEntryPointsForPackage(packagePath)); entryPoints.push(...this.getEntryPointsForPackage(packagePath));
// Also check for any nested node_modules in this package // Also check for any nested node_modules in this package
const nestedNodeModulesPath = const nestedNodeModulesPath = AbsoluteFsPath.resolve(packagePath, 'node_modules');
AbsoluteFsPath.from(path.resolve(packagePath, 'node_modules')); if (this.fs.exists(nestedNodeModulesPath)) {
if (fs.existsSync(nestedNodeModulesPath)) {
entryPoints.push(...this.walkDirectoryForEntryPoints(nestedNodeModulesPath)); entryPoints.push(...this.walkDirectoryForEntryPoints(nestedNodeModulesPath));
} }
} }
@ -114,14 +110,14 @@ export class EntryPointFinder {
const entryPoints: EntryPoint[] = []; const entryPoints: EntryPoint[] = [];
// Try to get an entry point from the top level package directory // Try to get an entry point from the top level package directory
const topLevelEntryPoint = getEntryPointInfo(this.logger, packagePath, packagePath); const topLevelEntryPoint = getEntryPointInfo(this.fs, this.logger, packagePath, packagePath);
if (topLevelEntryPoint !== null) { if (topLevelEntryPoint !== null) {
entryPoints.push(topLevelEntryPoint); entryPoints.push(topLevelEntryPoint);
} }
// Now search all the directories of this package for possible entry points // Now search all the directories of this package for possible entry points
this.walkDirectory(packagePath, subdir => { this.walkDirectory(packagePath, subdir => {
const subEntryPoint = getEntryPointInfo(this.logger, packagePath, subdir); const subEntryPoint = getEntryPointInfo(this.fs, this.logger, packagePath, subdir);
if (subEntryPoint !== null) { if (subEntryPoint !== null) {
entryPoints.push(subEntryPoint); entryPoints.push(subEntryPoint);
} }
@ -137,19 +133,19 @@ export class EntryPointFinder {
* @param fn the function to apply to each directory. * @param fn the function to apply to each directory.
*/ */
private walkDirectory(dir: AbsoluteFsPath, fn: (dir: AbsoluteFsPath) => void) { private walkDirectory(dir: AbsoluteFsPath, fn: (dir: AbsoluteFsPath) => void) {
return fs return this.fs
.readdirSync(dir) .readdir(dir)
// Not interested in hidden files // Not interested in hidden files
.filter(p => !p.startsWith('.')) .filter(p => !p.startsWith('.'))
// Ignore node_modules // Ignore node_modules
.filter(p => p !== 'node_modules') .filter(p => p !== 'node_modules')
// Only interested in directories (and only those that are not symlinks) // Only interested in directories (and only those that are not symlinks)
.filter(p => { .filter(p => {
const stat = fs.lstatSync(path.resolve(dir, p)); const stat = this.fs.lstat(AbsoluteFsPath.resolve(dir, p));
return stat.isDirectory() && !stat.isSymbolicLink(); return stat.isDirectory() && !stat.isSymbolicLink();
}) })
.forEach(subDir => { .forEach(subDir => {
const resolvedSubDir = AbsoluteFsPath.from(path.resolve(dir, subDir)); const resolvedSubDir = AbsoluteFsPath.resolve(dir, subDir);
fn(resolvedSubDir); fn(resolvedSubDir);
this.walkDirectory(resolvedSubDir, fn); this.walkDirectory(resolvedSubDir, fn);
}); });

View File

@ -0,0 +1,66 @@
/**
* @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 os from 'os';
import * as ts from 'typescript';
import {AbsoluteFsPath} from '../../../src/ngtsc/path';
import {FileSystem} from '../file_system/file_system';
export class NgccCompilerHost implements ts.CompilerHost {
private _caseSensitive = this.fs.exists(AbsoluteFsPath.fromUnchecked(__filename.toUpperCase()));
constructor(private fs: FileSystem, private options: ts.CompilerOptions) {}
getSourceFile(fileName: string, languageVersion: ts.ScriptTarget): ts.SourceFile|undefined {
const text = this.readFile(fileName);
return text !== undefined ? ts.createSourceFile(fileName, text, languageVersion) : undefined;
}
getDefaultLibFileName(options: ts.CompilerOptions): string {
return this.getDefaultLibLocation() + '/' + ts.getDefaultLibFileName(options);
}
getDefaultLibLocation(): string {
const nodeLibPath = AbsoluteFsPath.fromUnchecked(require.resolve('typescript'));
return AbsoluteFsPath.join(nodeLibPath, '..');
}
writeFile(fileName: string, data: string): void {
this.fs.writeFile(AbsoluteFsPath.fromUnchecked(fileName), data);
}
getCurrentDirectory(): string { return this.fs.pwd(); }
getCanonicalFileName(fileName: string): string {
return this.useCaseSensitiveFileNames ? fileName : fileName.toLowerCase();
}
useCaseSensitiveFileNames(): boolean { return this._caseSensitive; }
getNewLine(): string {
switch (this.options.newLine) {
case ts.NewLineKind.CarriageReturnLineFeed:
return '\r\n';
case ts.NewLineKind.LineFeed:
return '\n';
default:
return os.EOL;
}
}
fileExists(fileName: string): boolean {
return this.fs.exists(AbsoluteFsPath.fromUnchecked(fileName));
}
readFile(fileName: string): string|undefined {
if (!this.fileExists(fileName)) {
return undefined;
}
return this.fs.readFile(AbsoluteFsPath.fromUnchecked(fileName));
}
}

View File

@ -12,6 +12,7 @@ import {ModuleWithProvidersAnalyses, ModuleWithProvidersAnalyzer} from '../analy
import {NgccReferencesRegistry} from '../analysis/ngcc_references_registry'; import {NgccReferencesRegistry} from '../analysis/ngcc_references_registry';
import {ExportInfo, PrivateDeclarationsAnalyzer} from '../analysis/private_declarations_analyzer'; import {ExportInfo, PrivateDeclarationsAnalyzer} from '../analysis/private_declarations_analyzer';
import {SwitchMarkerAnalyses, SwitchMarkerAnalyzer} from '../analysis/switch_marker_analyzer'; import {SwitchMarkerAnalyses, SwitchMarkerAnalyzer} from '../analysis/switch_marker_analyzer';
import {FileSystem} from '../file_system/file_system';
import {Esm2015ReflectionHost} from '../host/esm2015_host'; import {Esm2015ReflectionHost} from '../host/esm2015_host';
import {Esm5ReflectionHost} from '../host/esm5_host'; import {Esm5ReflectionHost} from '../host/esm5_host';
import {NgccReflectionHost} from '../host/ngcc_host'; import {NgccReflectionHost} from '../host/ngcc_host';
@ -46,7 +47,7 @@ import {EntryPointBundle} from './entry_point_bundle';
* - Some formats may contain multiple "modules" in a single file. * - Some formats may contain multiple "modules" in a single file.
*/ */
export class Transformer { export class Transformer {
constructor(private logger: Logger) {} constructor(private fs: FileSystem, private logger: Logger) {}
/** /**
* Transform the source (and typings) files of a bundle. * Transform the source (and typings) files of a bundle.
@ -85,9 +86,9 @@ export class Transformer {
getRenderer(host: NgccReflectionHost, isCore: boolean, bundle: EntryPointBundle): Renderer { getRenderer(host: NgccReflectionHost, isCore: boolean, bundle: EntryPointBundle): Renderer {
switch (bundle.format) { switch (bundle.format) {
case 'esm2015': case 'esm2015':
return new EsmRenderer(this.logger, host, isCore, bundle); return new EsmRenderer(this.fs, this.logger, host, isCore, bundle);
case 'esm5': case 'esm5':
return new Esm5Renderer(this.logger, host, isCore, bundle); return new Esm5Renderer(this.fs, this.logger, host, isCore, bundle);
default: default:
throw new Error(`Renderer for "${bundle.format}" not yet implemented.`); throw new Error(`Renderer for "${bundle.format}" not yet implemented.`);
} }
@ -102,8 +103,8 @@ export class Transformer {
const switchMarkerAnalyses = switchMarkerAnalyzer.analyzeProgram(bundle.src.program); const switchMarkerAnalyses = switchMarkerAnalyzer.analyzeProgram(bundle.src.program);
const decorationAnalyzer = new DecorationAnalyzer( const decorationAnalyzer = new DecorationAnalyzer(
bundle.src.program, bundle.src.options, bundle.src.host, typeChecker, reflectionHost, this.fs, bundle.src.program, bundle.src.options, bundle.src.host, typeChecker,
referencesRegistry, bundle.rootDirs, isCore); reflectionHost, referencesRegistry, bundle.rootDirs, isCore);
const decorationAnalyses = decorationAnalyzer.analyzeProgram(); const decorationAnalyses = decorationAnalyzer.analyzeProgram();
const moduleWithProvidersAnalyzer = const moduleWithProvidersAnalyzer =

View File

@ -5,18 +5,21 @@
* Use of this source code is governed by an MIT-style license that can be * 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 * found in the LICENSE file at https://angular.io/license
*/ */
import * as ts from 'typescript';
import MagicString from 'magic-string'; import MagicString from 'magic-string';
import * as ts from 'typescript';
import {CompiledClass} from '../analysis/decoration_analyzer';
import {FileSystem} from '../file_system/file_system';
import {getIifeBody} from '../host/esm5_host'; import {getIifeBody} from '../host/esm5_host';
import {NgccReflectionHost} from '../host/ngcc_host'; import {NgccReflectionHost} from '../host/ngcc_host';
import {CompiledClass} from '../analysis/decoration_analyzer';
import {EsmRenderer} from './esm_renderer';
import {EntryPointBundle} from '../packages/entry_point_bundle';
import {Logger} from '../logging/logger'; import {Logger} from '../logging/logger';
import {EntryPointBundle} from '../packages/entry_point_bundle';
import {EsmRenderer} from './esm_renderer';
export class Esm5Renderer extends EsmRenderer { export class Esm5Renderer extends EsmRenderer {
constructor(logger: Logger, host: NgccReflectionHost, isCore: boolean, bundle: EntryPointBundle) { constructor(
super(logger, host, isCore, bundle); fs: FileSystem, logger: Logger, host: NgccReflectionHost, isCore: boolean,
bundle: EntryPointBundle) {
super(fs, logger, host, isCore, bundle);
} }
/** /**

View File

@ -5,20 +5,23 @@
* Use of this source code is governed by an MIT-style license that can be * 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 * found in the LICENSE file at https://angular.io/license
*/ */
import {dirname, relative} from 'canonical-path';
import MagicString from 'magic-string'; import MagicString from 'magic-string';
import * as ts from 'typescript'; import * as ts from 'typescript';
import {NgccReflectionHost, POST_R3_MARKER, PRE_R3_MARKER, SwitchableVariableDeclaration} from '../host/ngcc_host'; import {PathSegment, AbsoluteFsPath} from '../../../src/ngtsc/path';
import {CompiledClass} from '../analysis/decoration_analyzer';
import {RedundantDecoratorMap, Renderer, stripExtension} from './renderer';
import {EntryPointBundle} from '../packages/entry_point_bundle';
import {ExportInfo} from '../analysis/private_declarations_analyzer';
import {isDtsPath} from '../../../src/ngtsc/util/src/typescript'; import {isDtsPath} from '../../../src/ngtsc/util/src/typescript';
import {CompiledClass} from '../analysis/decoration_analyzer';
import {ExportInfo} from '../analysis/private_declarations_analyzer';
import {FileSystem} from '../file_system/file_system';
import {NgccReflectionHost, POST_R3_MARKER, PRE_R3_MARKER, SwitchableVariableDeclaration} from '../host/ngcc_host';
import {Logger} from '../logging/logger'; import {Logger} from '../logging/logger';
import {EntryPointBundle} from '../packages/entry_point_bundle';
import {RedundantDecoratorMap, Renderer, stripExtension} from './renderer';
export class EsmRenderer extends Renderer { export class EsmRenderer extends Renderer {
constructor(logger: Logger, host: NgccReflectionHost, isCore: boolean, bundle: EntryPointBundle) { constructor(
super(logger, host, isCore, bundle); fs: FileSystem, logger: Logger, host: NgccReflectionHost, isCore: boolean,
bundle: EntryPointBundle) {
super(fs, logger, host, isCore, bundle);
} }
/** /**
@ -33,7 +36,7 @@ export class EsmRenderer extends Renderer {
output.appendLeft(insertionPoint, renderedImports); output.appendLeft(insertionPoint, renderedImports);
} }
addExports(output: MagicString, entryPointBasePath: string, exports: ExportInfo[]): void { addExports(output: MagicString, entryPointBasePath: AbsoluteFsPath, exports: ExportInfo[]): void {
exports.forEach(e => { exports.forEach(e => {
let exportFrom = ''; let exportFrom = '';
const isDtsFile = isDtsPath(entryPointBasePath); const isDtsFile = isDtsPath(entryPointBasePath);
@ -41,7 +44,8 @@ export class EsmRenderer extends Renderer {
if (from) { if (from) {
const basePath = stripExtension(from); const basePath = stripExtension(from);
const relativePath = './' + relative(dirname(entryPointBasePath), basePath); const relativePath =
'./' + PathSegment.relative(AbsoluteFsPath.dirname(entryPointBasePath), basePath);
exportFrom = entryPointBasePath !== basePath ? ` from '${relativePath}'` : ''; exportFrom = entryPointBasePath !== basePath ? ` from '${relativePath}'` : '';
} }

View File

@ -6,25 +6,26 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {ConstantPool, Expression, Statement, WrappedNodeExpr, WritePropExpr} from '@angular/compiler'; import {ConstantPool, Expression, Statement, WrappedNodeExpr, WritePropExpr} from '@angular/compiler';
import {SourceMapConverter, commentRegex, fromJSON, fromMapFileSource, fromObject, fromSource, generateMapFileComment, mapFileCommentRegex, removeComments, removeMapFileComments} from 'convert-source-map'; import {SourceMapConverter, commentRegex, fromJSON, fromObject, fromSource, generateMapFileComment, mapFileCommentRegex, removeComments, removeMapFileComments} from 'convert-source-map';
import {readFileSync, statSync} from 'fs';
import MagicString from 'magic-string'; import MagicString from 'magic-string';
import {basename, dirname, relative, resolve} from 'canonical-path';
import {SourceMapConsumer, SourceMapGenerator, RawSourceMap} from 'source-map'; import {SourceMapConsumer, SourceMapGenerator, RawSourceMap} from 'source-map';
import * as ts from 'typescript'; import * as ts from 'typescript';
import {NoopImportRewriter, ImportRewriter, R3SymbolsImportRewriter, NOOP_DEFAULT_IMPORT_RECORDER} from '@angular/compiler-cli/src/ngtsc/imports'; import {NoopImportRewriter, ImportRewriter, R3SymbolsImportRewriter, NOOP_DEFAULT_IMPORT_RECORDER} from '../../../src/ngtsc/imports';
import {CompileResult} from '@angular/compiler-cli/src/ngtsc/transform'; import {AbsoluteFsPath, PathSegment} from '../../../src/ngtsc/path';
import {CompileResult} from '../../../src/ngtsc/transform';
import {translateStatement, translateType, ImportManager} from '../../../src/ngtsc/translator'; import {translateStatement, translateType, ImportManager} from '../../../src/ngtsc/translator';
import {NgccFlatImportRewriter} from './ngcc_import_rewriter';
import {CompiledClass, CompiledFile, DecorationAnalyses} from '../analysis/decoration_analyzer'; import {CompiledClass, CompiledFile, DecorationAnalyses} from '../analysis/decoration_analyzer';
import {ModuleWithProvidersInfo, ModuleWithProvidersAnalyses} from '../analysis/module_with_providers_analyzer'; import {ModuleWithProvidersInfo, ModuleWithProvidersAnalyses} from '../analysis/module_with_providers_analyzer';
import {PrivateDeclarationsAnalyses, ExportInfo} from '../analysis/private_declarations_analyzer'; import {PrivateDeclarationsAnalyses, ExportInfo} from '../analysis/private_declarations_analyzer';
import {SwitchMarkerAnalyses, SwitchMarkerAnalysis} from '../analysis/switch_marker_analyzer'; import {SwitchMarkerAnalyses, SwitchMarkerAnalysis} from '../analysis/switch_marker_analyzer';
import {IMPORT_PREFIX} from '../constants'; import {IMPORT_PREFIX} from '../constants';
import {FileSystem} from '../file_system/file_system';
import {NgccReflectionHost, SwitchableVariableDeclaration} from '../host/ngcc_host'; import {NgccReflectionHost, SwitchableVariableDeclaration} from '../host/ngcc_host';
import {EntryPointBundle} from '../packages/entry_point_bundle';
import {Logger} from '../logging/logger'; import {Logger} from '../logging/logger';
import {EntryPointBundle} from '../packages/entry_point_bundle';
import {NgccFlatImportRewriter} from './ngcc_import_rewriter';
interface SourceMapInfo { interface SourceMapInfo {
source: string; source: string;
@ -39,7 +40,7 @@ export interface FileInfo {
/** /**
* Path to where the file should be written. * Path to where the file should be written.
*/ */
path: string; path: AbsoluteFsPath;
/** /**
* The contents of the file to be be written. * The contents of the file to be be written.
*/ */
@ -81,8 +82,8 @@ export const RedundantDecoratorMap = Map;
*/ */
export abstract class Renderer { export abstract class Renderer {
constructor( constructor(
protected logger: Logger, protected host: NgccReflectionHost, protected isCore: boolean, protected fs: FileSystem, protected logger: Logger, protected host: NgccReflectionHost,
protected bundle: EntryPointBundle) {} protected isCore: boolean, protected bundle: EntryPointBundle) {}
renderProgram( renderProgram(
decorationAnalyses: DecorationAnalyses, switchMarkerAnalyses: SwitchMarkerAnalyses, decorationAnalyses: DecorationAnalyses, switchMarkerAnalyses: SwitchMarkerAnalyses,
@ -189,7 +190,7 @@ export abstract class Renderer {
this.addModuleWithProvidersParams(outputText, renderInfo.moduleWithProviders, importManager); this.addModuleWithProvidersParams(outputText, renderInfo.moduleWithProviders, importManager);
this.addImports(outputText, importManager.getAllImports(dtsFile.fileName), dtsFile); this.addImports(outputText, importManager.getAllImports(dtsFile.fileName), dtsFile);
this.addExports(outputText, dtsFile.fileName, renderInfo.privateExports); this.addExports(outputText, AbsoluteFsPath.fromSourceFile(dtsFile), renderInfo.privateExports);
return this.renderSourceAndMap(dtsFile, input, outputText); return this.renderSourceAndMap(dtsFile, input, outputText);
@ -207,11 +208,12 @@ export abstract class Renderer {
importManager: ImportManager): void { importManager: ImportManager): void {
moduleWithProviders.forEach(info => { moduleWithProviders.forEach(info => {
const ngModuleName = info.ngModule.node.name.text; const ngModuleName = info.ngModule.node.name.text;
const declarationFile = info.declaration.getSourceFile().fileName; const declarationFile = AbsoluteFsPath.fromSourceFile(info.declaration.getSourceFile());
const ngModuleFile = info.ngModule.node.getSourceFile().fileName; const ngModuleFile = AbsoluteFsPath.fromSourceFile(info.ngModule.node.getSourceFile());
const importPath = info.ngModule.viaModule || const importPath = info.ngModule.viaModule ||
(declarationFile !== ngModuleFile ? (declarationFile !== ngModuleFile ?
stripExtension(`./${relative(dirname(declarationFile), ngModuleFile)}`) : stripExtension(
`./${PathSegment.relative(AbsoluteFsPath.dirname(declarationFile), ngModuleFile)}`) :
null); null);
const ngModule = getImportString(importManager, importPath, ngModuleName); const ngModule = getImportString(importManager, importPath, ngModuleName);
@ -252,7 +254,7 @@ export abstract class Renderer {
output: MagicString, imports: {specifier: string, qualifier: string}[], output: MagicString, imports: {specifier: string, qualifier: string}[],
sf: ts.SourceFile): void; sf: ts.SourceFile): void;
protected abstract addExports( protected abstract addExports(
output: MagicString, entryPointBasePath: string, exports: ExportInfo[]): void; output: MagicString, entryPointBasePath: AbsoluteFsPath, exports: ExportInfo[]): void;
protected abstract addDefinitions( protected abstract addDefinitions(
output: MagicString, compiledClass: CompiledClass, definitions: string): void; output: MagicString, compiledClass: CompiledClass, definitions: string): void;
protected abstract removeDecorators( protected abstract removeDecorators(
@ -288,7 +290,7 @@ export abstract class Renderer {
*/ */
protected extractSourceMap(file: ts.SourceFile): SourceMapInfo { protected extractSourceMap(file: ts.SourceFile): SourceMapInfo {
const inline = commentRegex.test(file.text); const inline = commentRegex.test(file.text);
const external = mapFileCommentRegex.test(file.text); const external = mapFileCommentRegex.exec(file.text);
if (inline) { if (inline) {
const inlineSourceMap = fromSource(file.text); const inlineSourceMap = fromSource(file.text);
@ -300,17 +302,22 @@ export abstract class Renderer {
} else if (external) { } else if (external) {
let externalSourceMap: SourceMapConverter|null = null; let externalSourceMap: SourceMapConverter|null = null;
try { try {
externalSourceMap = fromMapFileSource(file.text, dirname(file.fileName)); const fileName = external[1] || external[2];
const filePath = AbsoluteFsPath.resolve(
AbsoluteFsPath.dirname(AbsoluteFsPath.fromSourceFile(file)), fileName);
const mappingFile = this.fs.readFile(filePath);
externalSourceMap = fromJSON(mappingFile);
} catch (e) { } catch (e) {
if (e.code === 'ENOENT') { if (e.code === 'ENOENT') {
this.logger.warn( this.logger.warn(
`The external map file specified in the source code comment "${e.path}" was not found on the file system.`); `The external map file specified in the source code comment "${e.path}" was not found on the file system.`);
const mapPath = file.fileName + '.map'; const mapPath = AbsoluteFsPath.fromUnchecked(file.fileName + '.map');
if (basename(e.path) !== basename(mapPath) && statSync(mapPath).isFile()) { if (PathSegment.basename(e.path) !== PathSegment.basename(mapPath) &&
this.fs.stat(mapPath).isFile()) {
this.logger.warn( this.logger.warn(
`Guessing the map file name from the source file name: "${basename(mapPath)}"`); `Guessing the map file name from the source file name: "${PathSegment.basename(mapPath)}"`);
try { try {
externalSourceMap = fromObject(JSON.parse(readFileSync(mapPath, 'utf8'))); externalSourceMap = fromObject(JSON.parse(this.fs.readFile(mapPath)));
} catch (e) { } catch (e) {
this.logger.error(e); this.logger.error(e);
} }
@ -333,9 +340,9 @@ export abstract class Renderer {
*/ */
protected renderSourceAndMap( protected renderSourceAndMap(
sourceFile: ts.SourceFile, input: SourceMapInfo, output: MagicString): FileInfo[] { sourceFile: ts.SourceFile, input: SourceMapInfo, output: MagicString): FileInfo[] {
const outputPath = sourceFile.fileName; const outputPath = AbsoluteFsPath.fromSourceFile(sourceFile);
const outputMapPath = `${outputPath}.map`; const outputMapPath = AbsoluteFsPath.fromUnchecked(`${outputPath}.map`);
const relativeSourcePath = basename(outputPath); const relativeSourcePath = PathSegment.basename(outputPath);
const relativeMapPath = `${relativeSourcePath}.map`; const relativeMapPath = `${relativeSourcePath}.map`;
const outputMap = output.generateMap({ const outputMap = output.generateMap({
@ -502,8 +509,8 @@ export function renderDefinitions(
return definitions; return definitions;
} }
export function stripExtension(filePath: string): string { export function stripExtension<T extends string>(filePath: T): T {
return filePath.replace(/\.(js|d\.ts)$/, ''); return filePath.replace(/\.(js|d\.ts)$/, '') as T;
} }
/** /**

View File

@ -6,15 +6,11 @@
* Use of this source code is governed by an MIT-style license that can be * 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 * found in the LICENSE file at https://angular.io/license
*/ */
import {AbsoluteFsPath} from '../../../src/ngtsc/path';
import {dirname} from 'canonical-path'; import {FileSystem} from '../file_system/file_system';
import {existsSync, writeFileSync} from 'fs';
import {mkdir, mv} from 'shelljs';
import {EntryPoint} from '../packages/entry_point'; import {EntryPoint} from '../packages/entry_point';
import {EntryPointBundle} from '../packages/entry_point_bundle'; import {EntryPointBundle} from '../packages/entry_point_bundle';
import {FileInfo} from '../rendering/renderer'; import {FileInfo} from '../rendering/renderer';
import {FileWriter} from './file_writer'; import {FileWriter} from './file_writer';
/** /**
@ -22,19 +18,22 @@ import {FileWriter} from './file_writer';
* a back-up of the original file with an extra `.bak` extension. * a back-up of the original file with an extra `.bak` extension.
*/ */
export class InPlaceFileWriter implements FileWriter { export class InPlaceFileWriter implements FileWriter {
constructor(protected fs: FileSystem) {}
writeBundle(_entryPoint: EntryPoint, _bundle: EntryPointBundle, transformedFiles: FileInfo[]) { writeBundle(_entryPoint: EntryPoint, _bundle: EntryPointBundle, transformedFiles: FileInfo[]) {
transformedFiles.forEach(file => this.writeFileAndBackup(file)); transformedFiles.forEach(file => this.writeFileAndBackup(file));
} }
protected writeFileAndBackup(file: FileInfo): void { protected writeFileAndBackup(file: FileInfo): void {
mkdir('-p', dirname(file.path)); this.fs.ensureDir(AbsoluteFsPath.dirname(file.path));
const backPath = file.path + '.__ivy_ngcc_bak'; const backPath = AbsoluteFsPath.fromUnchecked(`${file.path}.__ivy_ngcc_bak`);
if (existsSync(backPath)) { if (this.fs.exists(backPath)) {
throw new Error( throw new Error(
`Tried to overwrite ${backPath} with an ngcc back up file, which is disallowed.`); `Tried to overwrite ${backPath} with an ngcc back up file, which is disallowed.`);
} }
if (existsSync(file.path)) { if (this.fs.exists(file.path)) {
mv(file.path, backPath); this.fs.moveFile(file.path, backPath);
} }
writeFileSync(file.path, file.contents, 'utf8'); this.fs.writeFile(file.path, file.contents);
} }
} }

View File

@ -6,12 +6,7 @@
* Use of this source code is governed by an MIT-style license that can be * 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 * found in the LICENSE file at https://angular.io/license
*/ */
import {AbsoluteFsPath, PathSegment} from '../../../src/ngtsc/path';
import {dirname, join, relative} from 'canonical-path';
import {writeFileSync} from 'fs';
import {cp, mkdir} from 'shelljs';
import {AbsoluteFsPath} from '../../../src/ngtsc/path';
import {isDtsPath} from '../../../src/ngtsc/util/src/typescript'; import {isDtsPath} from '../../../src/ngtsc/util/src/typescript';
import {EntryPoint, EntryPointJsonProperty} from '../packages/entry_point'; import {EntryPoint, EntryPointJsonProperty} from '../packages/entry_point';
import {EntryPointBundle} from '../packages/entry_point_bundle'; import {EntryPointBundle} from '../packages/entry_point_bundle';
@ -32,7 +27,7 @@ const NGCC_DIRECTORY = '__ivy_ngcc__';
export class NewEntryPointFileWriter extends InPlaceFileWriter { export class NewEntryPointFileWriter extends InPlaceFileWriter {
writeBundle(entryPoint: EntryPoint, bundle: EntryPointBundle, transformedFiles: FileInfo[]) { writeBundle(entryPoint: EntryPoint, bundle: EntryPointBundle, transformedFiles: FileInfo[]) {
// The new folder is at the root of the overall package // The new folder is at the root of the overall package
const ngccFolder = AbsoluteFsPath.fromUnchecked(join(entryPoint.package, NGCC_DIRECTORY)); const ngccFolder = AbsoluteFsPath.join(entryPoint.package, NGCC_DIRECTORY);
this.copyBundle(bundle, entryPoint.package, ngccFolder); this.copyBundle(bundle, entryPoint.package, ngccFolder);
transformedFiles.forEach(file => this.writeFile(file, entryPoint.package, ngccFolder)); transformedFiles.forEach(file => this.writeFile(file, entryPoint.package, ngccFolder));
this.updatePackageJson(entryPoint, bundle.formatProperty, ngccFolder); this.updatePackageJson(entryPoint, bundle.formatProperty, ngccFolder);
@ -41,12 +36,13 @@ export class NewEntryPointFileWriter extends InPlaceFileWriter {
protected copyBundle( protected copyBundle(
bundle: EntryPointBundle, packagePath: AbsoluteFsPath, ngccFolder: AbsoluteFsPath) { bundle: EntryPointBundle, packagePath: AbsoluteFsPath, ngccFolder: AbsoluteFsPath) {
bundle.src.program.getSourceFiles().forEach(sourceFile => { bundle.src.program.getSourceFiles().forEach(sourceFile => {
const relativePath = relative(packagePath, sourceFile.fileName); const relativePath =
PathSegment.relative(packagePath, AbsoluteFsPath.fromSourceFile(sourceFile));
const isOutsidePackage = relativePath.startsWith('..'); const isOutsidePackage = relativePath.startsWith('..');
if (!sourceFile.isDeclarationFile && !isOutsidePackage) { if (!sourceFile.isDeclarationFile && !isOutsidePackage) {
const newFilePath = join(ngccFolder, relativePath); const newFilePath = AbsoluteFsPath.join(ngccFolder, relativePath);
mkdir('-p', dirname(newFilePath)); this.fs.ensureDir(AbsoluteFsPath.dirname(newFilePath));
cp(sourceFile.fileName, newFilePath); this.fs.copyFile(AbsoluteFsPath.fromSourceFile(sourceFile), newFilePath);
} }
}); });
} }
@ -57,19 +53,24 @@ export class NewEntryPointFileWriter extends InPlaceFileWriter {
// This is either `.d.ts` or `.d.ts.map` file // This is either `.d.ts` or `.d.ts.map` file
super.writeFileAndBackup(file); super.writeFileAndBackup(file);
} else { } else {
const relativePath = relative(packagePath, file.path); const relativePath = PathSegment.relative(packagePath, file.path);
const newFilePath = join(ngccFolder, relativePath); const newFilePath = AbsoluteFsPath.join(ngccFolder, relativePath);
mkdir('-p', dirname(newFilePath)); this.fs.ensureDir(AbsoluteFsPath.dirname(newFilePath));
writeFileSync(newFilePath, file.contents, 'utf8'); this.fs.writeFile(newFilePath, file.contents);
} }
} }
protected updatePackageJson( protected updatePackageJson(
entryPoint: EntryPoint, formatProperty: EntryPointJsonProperty, ngccFolder: AbsoluteFsPath) { entryPoint: EntryPoint, formatProperty: EntryPointJsonProperty, ngccFolder: AbsoluteFsPath) {
const formatPath = join(entryPoint.path, entryPoint.packageJson[formatProperty] !); const formatPath =
const newFormatPath = join(ngccFolder, relative(entryPoint.package, formatPath)); AbsoluteFsPath.join(entryPoint.path, entryPoint.packageJson[formatProperty] !);
const newFormatPath =
AbsoluteFsPath.join(ngccFolder, PathSegment.relative(entryPoint.package, formatPath));
const newFormatProperty = formatProperty + '_ivy_ngcc'; const newFormatProperty = formatProperty + '_ivy_ngcc';
(entryPoint.packageJson as any)[newFormatProperty] = relative(entryPoint.path, newFormatPath); (entryPoint.packageJson as any)[newFormatProperty] =
writeFileSync(join(entryPoint.path, 'package.json'), JSON.stringify(entryPoint.packageJson)); PathSegment.relative(entryPoint.path, newFormatPath);
this.fs.writeFile(
AbsoluteFsPath.join(entryPoint.path, 'package.json'),
JSON.stringify(entryPoint.packageJson));
} }
} }

View File

@ -12,6 +12,7 @@ import {Decorator} from '../../../src/ngtsc/reflection';
import {DecoratorHandler, DetectResult} from '../../../src/ngtsc/transform'; import {DecoratorHandler, DetectResult} from '../../../src/ngtsc/transform';
import {CompiledClass, DecorationAnalyses, DecorationAnalyzer} from '../../src/analysis/decoration_analyzer'; import {CompiledClass, DecorationAnalyses, DecorationAnalyzer} from '../../src/analysis/decoration_analyzer';
import {NgccReferencesRegistry} from '../../src/analysis/ngcc_references_registry'; import {NgccReferencesRegistry} from '../../src/analysis/ngcc_references_registry';
import {NodeJSFileSystem} from '../../src/file_system/node_js_file_system';
import {Esm2015ReflectionHost} from '../../src/host/esm2015_host'; import {Esm2015ReflectionHost} from '../../src/host/esm2015_host';
import {MockLogger} from '../helpers/mock_logger'; import {MockLogger} from '../helpers/mock_logger';
import {makeTestBundleProgram} from '../helpers/utils'; import {makeTestBundleProgram} from '../helpers/utils';
@ -136,8 +137,9 @@ describe('DecorationAnalyzer', () => {
const reflectionHost = const reflectionHost =
new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker()); new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker());
const referencesRegistry = new NgccReferencesRegistry(reflectionHost); const referencesRegistry = new NgccReferencesRegistry(reflectionHost);
const fs = new NodeJSFileSystem();
const analyzer = new DecorationAnalyzer( const analyzer = new DecorationAnalyzer(
program, options, host, program.getTypeChecker(), reflectionHost, referencesRegistry, fs, program, options, host, program.getTypeChecker(), reflectionHost, referencesRegistry,
[AbsoluteFsPath.fromUnchecked('/')], false); [AbsoluteFsPath.fromUnchecked('/')], false);
testHandler = createTestHandler(); testHandler = createTestHandler();
analyzer.handlers = [testHandler]; analyzer.handlers = [testHandler];

View File

@ -9,12 +9,15 @@
import * as ts from 'typescript'; import * as ts from 'typescript';
import {Reference} from '../../../src/ngtsc/imports'; import {Reference} from '../../../src/ngtsc/imports';
import {AbsoluteFsPath} from '../../../src/ngtsc/path';
import {NgccReferencesRegistry} from '../../src/analysis/ngcc_references_registry'; import {NgccReferencesRegistry} from '../../src/analysis/ngcc_references_registry';
import {PrivateDeclarationsAnalyzer} from '../../src/analysis/private_declarations_analyzer'; import {PrivateDeclarationsAnalyzer} from '../../src/analysis/private_declarations_analyzer';
import {Esm2015ReflectionHost} from '../../src/host/esm2015_host'; import {Esm2015ReflectionHost} from '../../src/host/esm2015_host';
import {MockLogger} from '../helpers/mock_logger'; import {MockLogger} from '../helpers/mock_logger';
import {getDeclaration, makeTestBundleProgram, makeTestProgram} from '../helpers/utils'; import {getDeclaration, makeTestBundleProgram, makeTestProgram} from '../helpers/utils';
const _ = AbsoluteFsPath.fromUnchecked;
describe('PrivateDeclarationsAnalyzer', () => { describe('PrivateDeclarationsAnalyzer', () => {
describe('analyzeProgram()', () => { describe('analyzeProgram()', () => {
@ -147,11 +150,11 @@ describe('PrivateDeclarationsAnalyzer', () => {
// not added to the ReferencesRegistry (i.e. they were not declared in an NgModule). // not added to the ReferencesRegistry (i.e. they were not declared in an NgModule).
expect(analyses.length).toEqual(2); expect(analyses.length).toEqual(2);
expect(analyses).toEqual([ expect(analyses).toEqual([
{identifier: 'PrivateComponent1', from: '/src/b.js', dtsFrom: null, alias: null}, {identifier: 'PrivateComponent1', from: _('/src/b.js'), dtsFrom: null, alias: null},
{ {
identifier: 'InternalComponent1', identifier: 'InternalComponent1',
from: '/src/c.js', from: _('/src/c.js'),
dtsFrom: '/typings/c.d.ts', dtsFrom: _('/typings/c.d.ts'),
alias: null alias: null
}, },
]); ]);
@ -207,7 +210,7 @@ describe('PrivateDeclarationsAnalyzer', () => {
const analyses = analyzer.analyzeProgram(program); const analyses = analyzer.analyzeProgram(program);
expect(analyses).toEqual([{ expect(analyses).toEqual([{
identifier: 'ComponentOne', identifier: 'ComponentOne',
from: '/src/a.js', from: _('/src/a.js'),
dtsFrom: null, dtsFrom: null,
alias: 'aliasedComponentOne', alias: 'aliasedComponentOne',
}]); }]);

View File

@ -5,8 +5,6 @@
* Use of this source code is governed by an MIT-style license that can be * 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 * found in the LICENSE file at https://angular.io/license
*/ */
import * as ts from 'typescript';
import {SwitchMarkerAnalyzer} from '../../src/analysis/switch_marker_analyzer'; import {SwitchMarkerAnalyzer} from '../../src/analysis/switch_marker_analyzer';
import {Esm2015ReflectionHost} from '../../src/host/esm2015_host'; import {Esm2015ReflectionHost} from '../../src/host/esm2015_host';
import {MockLogger} from '../helpers/mock_logger'; import {MockLogger} from '../helpers/mock_logger';

View File

@ -9,6 +9,7 @@ import {AbsoluteFsPath} from '../../../src/ngtsc/path';
import {DependencyResolver, SortedEntryPointsInfo} from '../../src/dependencies/dependency_resolver'; import {DependencyResolver, SortedEntryPointsInfo} from '../../src/dependencies/dependency_resolver';
import {EsmDependencyHost} from '../../src/dependencies/esm_dependency_host'; import {EsmDependencyHost} from '../../src/dependencies/esm_dependency_host';
import {ModuleResolver} from '../../src/dependencies/module_resolver'; import {ModuleResolver} from '../../src/dependencies/module_resolver';
import {NodeJSFileSystem} from '../../src/file_system/node_js_file_system';
import {EntryPoint} from '../../src/packages/entry_point'; import {EntryPoint} from '../../src/packages/entry_point';
import {MockLogger} from '../helpers/mock_logger'; import {MockLogger} from '../helpers/mock_logger';
@ -18,7 +19,8 @@ describe('DependencyResolver', () => {
let host: EsmDependencyHost; let host: EsmDependencyHost;
let resolver: DependencyResolver; let resolver: DependencyResolver;
beforeEach(() => { beforeEach(() => {
host = new EsmDependencyHost(new ModuleResolver()); const fs = new NodeJSFileSystem();
host = new EsmDependencyHost(fs, new ModuleResolver(fs));
resolver = new DependencyResolver(new MockLogger(), host); resolver = new DependencyResolver(new MockLogger(), host);
}); });
describe('sortEntryPointsByDependency()', () => { describe('sortEntryPointsByDependency()', () => {

View File

@ -11,12 +11,16 @@ import * as ts from 'typescript';
import {AbsoluteFsPath} from '../../../src/ngtsc/path'; import {AbsoluteFsPath} from '../../../src/ngtsc/path';
import {EsmDependencyHost} from '../../src/dependencies/esm_dependency_host'; import {EsmDependencyHost} from '../../src/dependencies/esm_dependency_host';
import {ModuleResolver} from '../../src/dependencies/module_resolver'; import {ModuleResolver} from '../../src/dependencies/module_resolver';
import {NodeJSFileSystem} from '../../src/file_system/node_js_file_system';
const _ = AbsoluteFsPath.from; const _ = AbsoluteFsPath.from;
describe('DependencyHost', () => { describe('DependencyHost', () => {
let host: EsmDependencyHost; let host: EsmDependencyHost;
beforeEach(() => host = new EsmDependencyHost(new ModuleResolver())); beforeEach(() => {
const fs = new NodeJSFileSystem();
host = new EsmDependencyHost(fs, new ModuleResolver(fs));
});
describe('getDependencies()', () => { describe('getDependencies()', () => {
beforeEach(createMockFileSystem); beforeEach(createMockFileSystem);
@ -94,13 +98,14 @@ describe('DependencyHost', () => {
}); });
it('should support `paths` alias mappings when resolving modules', () => { it('should support `paths` alias mappings when resolving modules', () => {
host = new EsmDependencyHost(new ModuleResolver({ const fs = new NodeJSFileSystem();
baseUrl: '/dist', host = new EsmDependencyHost(fs, new ModuleResolver(fs, {
paths: { baseUrl: '/dist',
'@app/*': ['*'], paths: {
'@lib/*/test': ['lib/*/test'], '@app/*': ['*'],
} '@lib/*/test': ['lib/*/test'],
})); }
}));
const {dependencies, missing, deepImports} = host.findDependencies(_('/path-alias/index.js')); const {dependencies, missing, deepImports} = host.findDependencies(_('/path-alias/index.js'));
expect(dependencies.size).toBe(4); expect(dependencies.size).toBe(4);
expect(dependencies.has(_('/dist/components'))).toBe(true); expect(dependencies.has(_('/dist/components'))).toBe(true);

View File

@ -10,6 +10,7 @@ import * as mockFs from 'mock-fs';
import {AbsoluteFsPath} from '../../../src/ngtsc/path'; import {AbsoluteFsPath} from '../../../src/ngtsc/path';
import {ModuleResolver, ResolvedDeepImport, ResolvedExternalModule, ResolvedRelativeModule} from '../../src/dependencies/module_resolver'; import {ModuleResolver, ResolvedDeepImport, ResolvedExternalModule, ResolvedRelativeModule} from '../../src/dependencies/module_resolver';
import {NodeJSFileSystem} from '../../src/file_system/node_js_file_system';
const _ = AbsoluteFsPath.from; const _ = AbsoluteFsPath.from;
@ -78,7 +79,8 @@ describe('ModuleResolver', () => {
describe('resolveModule()', () => { describe('resolveModule()', () => {
describe('with relative paths', () => { describe('with relative paths', () => {
it('should resolve sibling, child and aunt modules', () => { it('should resolve sibling, child and aunt modules', () => {
const resolver = new ModuleResolver(); const fs = new NodeJSFileSystem();
const resolver = new ModuleResolver(fs);
expect(resolver.resolveModuleImport('./x', _('/libs/local-package/index.js'))) expect(resolver.resolveModuleImport('./x', _('/libs/local-package/index.js')))
.toEqual(new ResolvedRelativeModule(_('/libs/local-package/x.js'))); .toEqual(new ResolvedRelativeModule(_('/libs/local-package/x.js')));
expect(resolver.resolveModuleImport('./sub-folder', _('/libs/local-package/index.js'))) expect(resolver.resolveModuleImport('./sub-folder', _('/libs/local-package/index.js')))
@ -88,14 +90,16 @@ describe('ModuleResolver', () => {
}); });
it('should return `null` if the resolved module relative module does not exist', () => { it('should return `null` if the resolved module relative module does not exist', () => {
const resolver = new ModuleResolver(); const fs = new NodeJSFileSystem();
const resolver = new ModuleResolver(fs);
expect(resolver.resolveModuleImport('./y', _('/libs/local-package/index.js'))).toBe(null); expect(resolver.resolveModuleImport('./y', _('/libs/local-package/index.js'))).toBe(null);
}); });
}); });
describe('with non-mapped external paths', () => { describe('with non-mapped external paths', () => {
it('should resolve to the package.json of a local node_modules package', () => { it('should resolve to the package.json of a local node_modules package', () => {
const resolver = new ModuleResolver(); const fs = new NodeJSFileSystem();
const resolver = new ModuleResolver(fs);
expect(resolver.resolveModuleImport('package-1', _('/libs/local-package/index.js'))) expect(resolver.resolveModuleImport('package-1', _('/libs/local-package/index.js')))
.toEqual(new ResolvedExternalModule(_('/libs/local-package/node_modules/package-1'))); .toEqual(new ResolvedExternalModule(_('/libs/local-package/node_modules/package-1')));
expect( expect(
@ -106,7 +110,8 @@ describe('ModuleResolver', () => {
}); });
it('should resolve to the package.json of a higher node_modules package', () => { it('should resolve to the package.json of a higher node_modules package', () => {
const resolver = new ModuleResolver(); const fs = new NodeJSFileSystem();
const resolver = new ModuleResolver(fs);
expect(resolver.resolveModuleImport('package-2', _('/libs/local-package/index.js'))) expect(resolver.resolveModuleImport('package-2', _('/libs/local-package/index.js')))
.toEqual(new ResolvedExternalModule(_('/libs/node_modules/package-2'))); .toEqual(new ResolvedExternalModule(_('/libs/node_modules/package-2')));
expect(resolver.resolveModuleImport('top-package', _('/libs/local-package/index.js'))) expect(resolver.resolveModuleImport('top-package', _('/libs/local-package/index.js')))
@ -114,20 +119,23 @@ describe('ModuleResolver', () => {
}); });
it('should return `null` if the package cannot be found', () => { it('should return `null` if the package cannot be found', () => {
const resolver = new ModuleResolver(); const fs = new NodeJSFileSystem();
const resolver = new ModuleResolver(fs);
expect(resolver.resolveModuleImport('missing-2', _('/libs/local-package/index.js'))) expect(resolver.resolveModuleImport('missing-2', _('/libs/local-package/index.js')))
.toBe(null); .toBe(null);
}); });
it('should return `null` if the package is not accessible because it is in a inner node_modules package', it('should return `null` if the package is not accessible because it is in a inner node_modules package',
() => { () => {
const resolver = new ModuleResolver(); const fs = new NodeJSFileSystem();
const resolver = new ModuleResolver(fs);
expect(resolver.resolveModuleImport('package-3', _('/libs/local-package/index.js'))) expect(resolver.resolveModuleImport('package-3', _('/libs/local-package/index.js')))
.toBe(null); .toBe(null);
}); });
it('should identify deep imports into an external module', () => { it('should identify deep imports into an external module', () => {
const resolver = new ModuleResolver(); const fs = new NodeJSFileSystem();
const resolver = new ModuleResolver(fs);
expect( expect(
resolver.resolveModuleImport('package-1/sub-folder', _('/libs/local-package/index.js'))) resolver.resolveModuleImport('package-1/sub-folder', _('/libs/local-package/index.js')))
.toEqual( .toEqual(
@ -137,8 +145,9 @@ describe('ModuleResolver', () => {
describe('with mapped path external modules', () => { describe('with mapped path external modules', () => {
it('should resolve to the package.json of simple mapped packages', () => { it('should resolve to the package.json of simple mapped packages', () => {
const fs = new NodeJSFileSystem();
const resolver = const resolver =
new ModuleResolver({baseUrl: '/dist', paths: {'*': ['*', 'sub-folder/*']}}); new ModuleResolver(fs, {baseUrl: '/dist', paths: {'*': ['*', 'sub-folder/*']}});
expect(resolver.resolveModuleImport('package-4', _('/libs/local-package/index.js'))) expect(resolver.resolveModuleImport('package-4', _('/libs/local-package/index.js')))
.toEqual(new ResolvedExternalModule(_('/dist/package-4'))); .toEqual(new ResolvedExternalModule(_('/dist/package-4')));
@ -148,7 +157,8 @@ describe('ModuleResolver', () => {
}); });
it('should select the best match by the length of prefix before the *', () => { it('should select the best match by the length of prefix before the *', () => {
const resolver = new ModuleResolver({ const fs = new NodeJSFileSystem();
const resolver = new ModuleResolver(fs, {
baseUrl: '/dist', baseUrl: '/dist',
paths: { paths: {
'@lib/*': ['*'], '@lib/*': ['*'],
@ -166,25 +176,28 @@ describe('ModuleResolver', () => {
it('should follow the ordering of `paths` when matching mapped packages', () => { it('should follow the ordering of `paths` when matching mapped packages', () => {
let resolver: ModuleResolver; let resolver: ModuleResolver;
resolver = new ModuleResolver({baseUrl: '/dist', paths: {'*': ['*', 'sub-folder/*']}}); const fs = new NodeJSFileSystem();
resolver = new ModuleResolver(fs, {baseUrl: '/dist', paths: {'*': ['*', 'sub-folder/*']}});
expect(resolver.resolveModuleImport('package-4', _('/libs/local-package/index.js'))) expect(resolver.resolveModuleImport('package-4', _('/libs/local-package/index.js')))
.toEqual(new ResolvedExternalModule(_('/dist/package-4'))); .toEqual(new ResolvedExternalModule(_('/dist/package-4')));
resolver = new ModuleResolver({baseUrl: '/dist', paths: {'*': ['sub-folder/*', '*']}}); resolver = new ModuleResolver(fs, {baseUrl: '/dist', paths: {'*': ['sub-folder/*', '*']}});
expect(resolver.resolveModuleImport('package-4', _('/libs/local-package/index.js'))) expect(resolver.resolveModuleImport('package-4', _('/libs/local-package/index.js')))
.toEqual(new ResolvedExternalModule(_('/dist/sub-folder/package-4'))); .toEqual(new ResolvedExternalModule(_('/dist/sub-folder/package-4')));
}); });
it('should resolve packages when the path mappings have post-fixes', () => { it('should resolve packages when the path mappings have post-fixes', () => {
const fs = new NodeJSFileSystem();
const resolver = const resolver =
new ModuleResolver({baseUrl: '/dist', paths: {'*': ['sub-folder/*/post-fix']}}); new ModuleResolver(fs, {baseUrl: '/dist', paths: {'*': ['sub-folder/*/post-fix']}});
expect(resolver.resolveModuleImport('package-5', _('/libs/local-package/index.js'))) expect(resolver.resolveModuleImport('package-5', _('/libs/local-package/index.js')))
.toEqual(new ResolvedExternalModule(_('/dist/sub-folder/package-5/post-fix'))); .toEqual(new ResolvedExternalModule(_('/dist/sub-folder/package-5/post-fix')));
}); });
it('should match paths against complex path matchers', () => { it('should match paths against complex path matchers', () => {
const fs = new NodeJSFileSystem();
const resolver = const resolver =
new ModuleResolver({baseUrl: '/dist', paths: {'@shared/*': ['sub-folder/*']}}); new ModuleResolver(fs, {baseUrl: '/dist', paths: {'@shared/*': ['sub-folder/*']}});
expect(resolver.resolveModuleImport('@shared/package-4', _('/libs/local-package/index.js'))) expect(resolver.resolveModuleImport('@shared/package-4', _('/libs/local-package/index.js')))
.toEqual(new ResolvedExternalModule(_('/dist/sub-folder/package-4'))); .toEqual(new ResolvedExternalModule(_('/dist/sub-folder/package-4')));
expect(resolver.resolveModuleImport('package-5', _('/libs/local-package/index.js'))) expect(resolver.resolveModuleImport('package-5', _('/libs/local-package/index.js')))
@ -193,15 +206,17 @@ describe('ModuleResolver', () => {
it('should resolve path as "relative" if the mapped path is inside the current package', it('should resolve path as "relative" if the mapped path is inside the current package',
() => { () => {
const resolver = new ModuleResolver({baseUrl: '/dist', paths: {'@shared/*': ['*']}}); const fs = new NodeJSFileSystem();
const resolver = new ModuleResolver(fs, {baseUrl: '/dist', paths: {'@shared/*': ['*']}});
expect(resolver.resolveModuleImport( expect(resolver.resolveModuleImport(
'@shared/package-4/x', _('/dist/package-4/sub-folder/index.js'))) '@shared/package-4/x', _('/dist/package-4/sub-folder/index.js')))
.toEqual(new ResolvedRelativeModule(_('/dist/package-4/x.js'))); .toEqual(new ResolvedRelativeModule(_('/dist/package-4/x.js')));
}); });
it('should resolve paths where the wildcard matches more than one path segment', () => { it('should resolve paths where the wildcard matches more than one path segment', () => {
const resolver = const fs = new NodeJSFileSystem();
new ModuleResolver({baseUrl: '/dist', paths: {'@shared/*/post-fix': ['*/post-fix']}}); const resolver = new ModuleResolver(
fs, {baseUrl: '/dist', paths: {'@shared/*/post-fix': ['*/post-fix']}});
expect( expect(
resolver.resolveModuleImport( resolver.resolveModuleImport(
'@shared/sub-folder/package-5/post-fix', _('/dist/package-4/sub-folder/index.js'))) '@shared/sub-folder/package-5/post-fix', _('/dist/package-4/sub-folder/index.js')))

View File

@ -15,6 +15,7 @@ import {EntryPointBundle} from '../../src/packages/entry_point_bundle';
export {getDeclaration} from '../../../src/ngtsc/testing/in_memory_typescript'; export {getDeclaration} from '../../../src/ngtsc/testing/in_memory_typescript';
const _ = AbsoluteFsPath.fromUnchecked;
/** /**
* *
* @param format The format of the bundle. * @param format The format of the bundle.
@ -28,11 +29,7 @@ export function makeTestEntryPointBundle(
const src = makeTestBundleProgram(files); const src = makeTestBundleProgram(files);
const dts = dtsFiles ? makeTestBundleProgram(dtsFiles) : null; const dts = dtsFiles ? makeTestBundleProgram(dtsFiles) : null;
const isFlatCore = isCore && src.r3SymbolsFile === null; const isFlatCore = isCore && src.r3SymbolsFile === null;
return { return {formatProperty, format, rootDirs: [_('/')], src, dts, isCore, isFlatCore};
formatProperty,
format,
rootDirs: [AbsoluteFsPath.fromUnchecked('/')], src, dts, isCore, isFlatCore
};
} }
/** /**
@ -41,10 +38,10 @@ export function makeTestEntryPointBundle(
*/ */
export function makeTestBundleProgram(files: {name: string, contents: string}[]): BundleProgram { export function makeTestBundleProgram(files: {name: string, contents: string}[]): BundleProgram {
const {program, options, host} = makeTestProgramInternal(...files); const {program, options, host} = makeTestProgramInternal(...files);
const path = files[0].name; const path = _(files[0].name);
const file = program.getSourceFile(path) !; const file = program.getSourceFile(path) !;
const r3SymbolsInfo = files.find(file => file.name.indexOf('r3_symbols') !== -1) || null; const r3SymbolsInfo = files.find(file => file.name.indexOf('r3_symbols') !== -1) || null;
const r3SymbolsPath = r3SymbolsInfo && r3SymbolsInfo.name; const r3SymbolsPath = r3SymbolsInfo && _(r3SymbolsInfo.name);
const r3SymbolsFile = r3SymbolsPath && program.getSourceFile(r3SymbolsPath) || null; const r3SymbolsFile = r3SymbolsPath && program.getSourceFile(r3SymbolsPath) || null;
return {program, options, host, path, file, r3SymbolsPath, r3SymbolsFile}; return {program, options, host, path, file, r3SymbolsPath, r3SymbolsFile};
} }

View File

@ -8,7 +8,7 @@
import * as ts from 'typescript'; import * as ts from 'typescript';
import {ClassMemberKind, Import, isNamedClassDeclaration, isNamedFunctionDeclaration, isNamedVariableDeclaration} from '../../../src/ngtsc/reflection'; import {ClassMemberKind, CtorParameter, Import, isNamedClassDeclaration, isNamedFunctionDeclaration, isNamedVariableDeclaration} from '../../../src/ngtsc/reflection';
import {Esm2015ReflectionHost} from '../../src/host/esm2015_host'; import {Esm2015ReflectionHost} from '../../src/host/esm2015_host';
import {MockLogger} from '../helpers/mock_logger'; import {MockLogger} from '../helpers/mock_logger';
import {getDeclaration, makeTestBundleProgram, makeTestProgram} from '../helpers/utils'; import {getDeclaration, makeTestBundleProgram, makeTestProgram} from '../helpers/utils';
@ -1007,7 +1007,7 @@ describe('Esm2015ReflectionHost', () => {
const parameters = host.getConstructorParameters(classNode) !; const parameters = host.getConstructorParameters(classNode) !;
expect(parameters.length).toBe(1); expect(parameters.length).toBe(1);
expect(parameters[0]).toEqual(jasmine.objectContaining({ expect(parameters[0]).toEqual(jasmine.objectContaining<CtorParameter>({
name: 'arg1', name: 'arg1',
decorators: [], decorators: [],
})); }));
@ -1021,7 +1021,7 @@ describe('Esm2015ReflectionHost', () => {
const parameters = host.getConstructorParameters(classNode) !; const parameters = host.getConstructorParameters(classNode) !;
expect(parameters.length).toBe(1); expect(parameters.length).toBe(1);
expect(parameters[0]).toEqual(jasmine.objectContaining({ expect(parameters[0]).toEqual(jasmine.objectContaining<CtorParameter>({
name: 'arg1', name: 'arg1',
decorators: null, decorators: null,
})); }));
@ -1035,7 +1035,7 @@ describe('Esm2015ReflectionHost', () => {
const parameters = host.getConstructorParameters(classNode) !; const parameters = host.getConstructorParameters(classNode) !;
expect(parameters.length).toBe(1); expect(parameters.length).toBe(1);
expect(parameters[0]).toEqual(jasmine.objectContaining({ expect(parameters[0]).toEqual(jasmine.objectContaining<CtorParameter>({
name: 'arg1', name: 'arg1',
decorators: null, decorators: null,
})); }));
@ -1115,11 +1115,11 @@ describe('Esm2015ReflectionHost', () => {
const parameters = host.getConstructorParameters(classNode); const parameters = host.getConstructorParameters(classNode);
expect(parameters !.length).toBe(2); expect(parameters !.length).toBe(2);
expect(parameters ![0]).toEqual(jasmine.objectContaining({ expect(parameters ![0]).toEqual(jasmine.objectContaining<CtorParameter>({
name: 'arg1', name: 'arg1',
decorators: null, decorators: null,
})); }));
expect(parameters ![1]).toEqual(jasmine.objectContaining({ expect(parameters ![1]).toEqual(jasmine.objectContaining<CtorParameter>({
name: 'arg2', name: 'arg2',
decorators: jasmine.any(Array) as any decorators: jasmine.any(Array) as any
})); }));

View File

@ -12,6 +12,7 @@ import * as mockFs from 'mock-fs';
import {join} from 'path'; import {join} from 'path';
import {getAngularPackagesFromRunfiles, resolveNpmTreeArtifact} from '../../../test/runfile_helpers'; import {getAngularPackagesFromRunfiles, resolveNpmTreeArtifact} from '../../../test/runfile_helpers';
import {NodeJSFileSystem} from '../../src/file_system/node_js_file_system';
import {mainNgcc} from '../../src/main'; import {mainNgcc} from '../../src/main';
import {markAsProcessed} from '../../src/packages/build_marker'; import {markAsProcessed} from '../../src/packages/build_marker';
import {EntryPointJsonProperty, EntryPointPackageJson, SUPPORTED_FORMAT_PROPERTIES} from '../../src/packages/entry_point'; import {EntryPointJsonProperty, EntryPointPackageJson, SUPPORTED_FORMAT_PROPERTIES} from '../../src/packages/entry_point';
@ -144,8 +145,10 @@ describe('ngcc main()', () => {
const basePath = '/node_modules'; const basePath = '/node_modules';
const targetPackageJsonPath = _(join(basePath, packagePath, 'package.json')); const targetPackageJsonPath = _(join(basePath, packagePath, 'package.json'));
const targetPackage = loadPackage(packagePath); const targetPackage = loadPackage(packagePath);
markAsProcessed(targetPackage, targetPackageJsonPath, 'typings'); const fs = new NodeJSFileSystem();
properties.forEach(property => markAsProcessed(targetPackage, targetPackageJsonPath, property)); markAsProcessed(fs, targetPackage, targetPackageJsonPath, 'typings');
properties.forEach(
property => markAsProcessed(fs, targetPackage, targetPackageJsonPath, property));
} }

View File

@ -6,12 +6,12 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {readFileSync, writeFileSync} from 'fs'; import {readFileSync} from 'fs';
import * as mockFs from 'mock-fs'; import * as mockFs from 'mock-fs';
import {AbsoluteFsPath} from '../../../src/ngtsc/path'; import {AbsoluteFsPath} from '../../../src/ngtsc/path';
import {NodeJSFileSystem} from '../../src/file_system/node_js_file_system';
import {hasBeenProcessed, markAsProcessed} from '../../src/packages/build_marker'; import {hasBeenProcessed, markAsProcessed} from '../../src/packages/build_marker';
import {EntryPoint} from '../../src/packages/entry_point';
function createMockFileSystem() { function createMockFileSystem() {
mockFs({ mockFs({
@ -106,21 +106,24 @@ describe('Marker files', () => {
expect(pkg.__processed_by_ivy_ngcc__).toBeUndefined(); expect(pkg.__processed_by_ivy_ngcc__).toBeUndefined();
expect(pkg.__processed_by_ivy_ngcc__).toBeUndefined(); expect(pkg.__processed_by_ivy_ngcc__).toBeUndefined();
markAsProcessed(pkg, COMMON_PACKAGE_PATH, 'fesm2015'); const fs = new NodeJSFileSystem();
markAsProcessed(fs, pkg, COMMON_PACKAGE_PATH, 'fesm2015');
pkg = JSON.parse(readFileSync(COMMON_PACKAGE_PATH, 'utf8')); pkg = JSON.parse(readFileSync(COMMON_PACKAGE_PATH, 'utf8'));
expect(pkg.__processed_by_ivy_ngcc__.fesm2015).toEqual('0.0.0-PLACEHOLDER'); expect(pkg.__processed_by_ivy_ngcc__.fesm2015).toEqual('0.0.0-PLACEHOLDER');
expect(pkg.__processed_by_ivy_ngcc__.esm5).toBeUndefined(); expect(pkg.__processed_by_ivy_ngcc__.esm5).toBeUndefined();
markAsProcessed(pkg, COMMON_PACKAGE_PATH, 'esm5'); markAsProcessed(fs, pkg, COMMON_PACKAGE_PATH, 'esm5');
pkg = JSON.parse(readFileSync(COMMON_PACKAGE_PATH, 'utf8')); pkg = JSON.parse(readFileSync(COMMON_PACKAGE_PATH, 'utf8'));
expect(pkg.__processed_by_ivy_ngcc__.fesm2015).toEqual('0.0.0-PLACEHOLDER'); expect(pkg.__processed_by_ivy_ngcc__.fesm2015).toEqual('0.0.0-PLACEHOLDER');
expect(pkg.__processed_by_ivy_ngcc__.esm5).toEqual('0.0.0-PLACEHOLDER'); expect(pkg.__processed_by_ivy_ngcc__.esm5).toEqual('0.0.0-PLACEHOLDER');
}); });
it('should update the packageJson object in-place', () => { it('should update the packageJson object in-place', () => {
const fs = new NodeJSFileSystem();
let pkg = JSON.parse(readFileSync(COMMON_PACKAGE_PATH, 'utf8')); let pkg = JSON.parse(readFileSync(COMMON_PACKAGE_PATH, 'utf8'));
expect(pkg.__processed_by_ivy_ngcc__).toBeUndefined(); expect(pkg.__processed_by_ivy_ngcc__).toBeUndefined();
markAsProcessed(pkg, COMMON_PACKAGE_PATH, 'fesm2015'); markAsProcessed(fs, pkg, COMMON_PACKAGE_PATH, 'fesm2015');
expect(pkg.__processed_by_ivy_ngcc__.fesm2015).toEqual('0.0.0-PLACEHOLDER'); expect(pkg.__processed_by_ivy_ngcc__.fesm2015).toEqual('0.0.0-PLACEHOLDER');
}); });
}); });

View File

@ -12,6 +12,7 @@ import {AbsoluteFsPath} from '../../../src/ngtsc/path';
import {DependencyResolver} from '../../src/dependencies/dependency_resolver'; import {DependencyResolver} from '../../src/dependencies/dependency_resolver';
import {EsmDependencyHost} from '../../src/dependencies/esm_dependency_host'; import {EsmDependencyHost} from '../../src/dependencies/esm_dependency_host';
import {ModuleResolver} from '../../src/dependencies/module_resolver'; import {ModuleResolver} from '../../src/dependencies/module_resolver';
import {NodeJSFileSystem} from '../../src/file_system/node_js_file_system';
import {EntryPoint} from '../../src/packages/entry_point'; import {EntryPoint} from '../../src/packages/entry_point';
import {EntryPointFinder} from '../../src/packages/entry_point_finder'; import {EntryPointFinder} from '../../src/packages/entry_point_finder';
import {MockLogger} from '../helpers/mock_logger'; import {MockLogger} from '../helpers/mock_logger';
@ -22,12 +23,13 @@ describe('findEntryPoints()', () => {
let resolver: DependencyResolver; let resolver: DependencyResolver;
let finder: EntryPointFinder; let finder: EntryPointFinder;
beforeEach(() => { beforeEach(() => {
const fs = new NodeJSFileSystem();
resolver = resolver =
new DependencyResolver(new MockLogger(), new EsmDependencyHost(new ModuleResolver())); new DependencyResolver(new MockLogger(), new EsmDependencyHost(fs, new ModuleResolver(fs)));
spyOn(resolver, 'sortEntryPointsByDependency').and.callFake((entryPoints: EntryPoint[]) => { spyOn(resolver, 'sortEntryPointsByDependency').and.callFake((entryPoints: EntryPoint[]) => {
return {entryPoints, ignoredEntryPoints: [], ignoredDependencies: []}; return {entryPoints, ignoredEntryPoints: [], ignoredDependencies: []};
}); });
finder = new EntryPointFinder(new MockLogger(), resolver); finder = new EntryPointFinder(fs, new MockLogger(), resolver);
}); });
beforeEach(createMockFileSystem); beforeEach(createMockFileSystem);
afterEach(restoreRealFileSystem); afterEach(restoreRealFileSystem);

View File

@ -6,24 +6,27 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {AbsoluteFsPath} from '@angular/compiler-cli/src/ngtsc/path';
import {readFileSync} from 'fs'; import {readFileSync} from 'fs';
import * as mockFs from 'mock-fs'; import * as mockFs from 'mock-fs';
import {AbsoluteFsPath} from '../../../src/ngtsc/path';
import {NodeJSFileSystem} from '../../src/file_system/node_js_file_system';
import {getEntryPointInfo} from '../../src/packages/entry_point'; import {getEntryPointInfo} from '../../src/packages/entry_point';
import {MockLogger} from '../helpers/mock_logger'; import {MockLogger} from '../helpers/mock_logger';
const _ = AbsoluteFsPath.fromUnchecked;
describe('getEntryPointInfo()', () => { describe('getEntryPointInfo()', () => {
beforeEach(createMockFileSystem); beforeEach(createMockFileSystem);
afterEach(restoreRealFileSystem); afterEach(restoreRealFileSystem);
const _ = AbsoluteFsPath.from;
const SOME_PACKAGE = _('/some_package'); const SOME_PACKAGE = _('/some_package');
it('should return an object containing absolute paths to the formats of the specified entry-point', it('should return an object containing absolute paths to the formats of the specified entry-point',
() => { () => {
const entryPoint = const fs = new NodeJSFileSystem();
getEntryPointInfo(new MockLogger(), SOME_PACKAGE, _('/some_package/valid_entry_point')); const entryPoint = getEntryPointInfo(
fs, new MockLogger(), SOME_PACKAGE, _('/some_package/valid_entry_point'));
expect(entryPoint).toEqual({ expect(entryPoint).toEqual({
name: 'some-package/valid_entry_point', name: 'some-package/valid_entry_point',
package: SOME_PACKAGE, package: SOME_PACKAGE,
@ -35,21 +38,24 @@ describe('getEntryPointInfo()', () => {
}); });
it('should return null if there is no package.json at the entry-point path', () => { it('should return null if there is no package.json at the entry-point path', () => {
const entryPoint = const fs = new NodeJSFileSystem();
getEntryPointInfo(new MockLogger(), SOME_PACKAGE, _('/some_package/missing_package_json')); const entryPoint = getEntryPointInfo(
fs, new MockLogger(), SOME_PACKAGE, _('/some_package/missing_package_json'));
expect(entryPoint).toBe(null); expect(entryPoint).toBe(null);
}); });
it('should return null if there is no typings or types field in the package.json', () => { it('should return null if there is no typings or types field in the package.json', () => {
const fs = new NodeJSFileSystem();
const entryPoint = const entryPoint =
getEntryPointInfo(new MockLogger(), SOME_PACKAGE, _('/some_package/missing_typings')); getEntryPointInfo(fs, new MockLogger(), SOME_PACKAGE, _('/some_package/missing_typings'));
expect(entryPoint).toBe(null); expect(entryPoint).toBe(null);
}); });
it('should return an object with `compiledByAngular` set to false if there is no metadata.json file next to the typing file', it('should return an object with `compiledByAngular` set to false if there is no metadata.json file next to the typing file',
() => { () => {
const entryPoint = const fs = new NodeJSFileSystem();
getEntryPointInfo(new MockLogger(), SOME_PACKAGE, _('/some_package/missing_metadata')); const entryPoint = getEntryPointInfo(
fs, new MockLogger(), SOME_PACKAGE, _('/some_package/missing_metadata'));
expect(entryPoint).toEqual({ expect(entryPoint).toEqual({
name: 'some-package/missing_metadata', name: 'some-package/missing_metadata',
package: SOME_PACKAGE, package: SOME_PACKAGE,
@ -61,8 +67,9 @@ describe('getEntryPointInfo()', () => {
}); });
it('should work if the typings field is named `types', () => { it('should work if the typings field is named `types', () => {
const fs = new NodeJSFileSystem();
const entryPoint = getEntryPointInfo( const entryPoint = getEntryPointInfo(
new MockLogger(), SOME_PACKAGE, _('/some_package/types_rather_than_typings')); fs, new MockLogger(), SOME_PACKAGE, _('/some_package/types_rather_than_typings'));
expect(entryPoint).toEqual({ expect(entryPoint).toEqual({
name: 'some-package/types_rather_than_typings', name: 'some-package/types_rather_than_typings',
package: SOME_PACKAGE, package: SOME_PACKAGE,
@ -74,8 +81,9 @@ describe('getEntryPointInfo()', () => {
}); });
it('should work with Angular Material style package.json', () => { it('should work with Angular Material style package.json', () => {
const fs = new NodeJSFileSystem();
const entryPoint = const entryPoint =
getEntryPointInfo(new MockLogger(), SOME_PACKAGE, _('/some_package/material_style')); getEntryPointInfo(fs, new MockLogger(), SOME_PACKAGE, _('/some_package/material_style'));
expect(entryPoint).toEqual({ expect(entryPoint).toEqual({
name: 'some_package/material_style', name: 'some_package/material_style',
package: SOME_PACKAGE, package: SOME_PACKAGE,
@ -87,8 +95,9 @@ describe('getEntryPointInfo()', () => {
}); });
it('should return null if the package.json is not valid JSON', () => { it('should return null if the package.json is not valid JSON', () => {
const entryPoint = const fs = new NodeJSFileSystem();
getEntryPointInfo(new MockLogger(), SOME_PACKAGE, _('/some_package/unexpected_symbols')); const entryPoint = getEntryPointInfo(
fs, new MockLogger(), SOME_PACKAGE, _('/some_package/unexpected_symbols'));
expect(entryPoint).toBe(null); expect(entryPoint).toBe(null);
}); });
}); });

View File

@ -5,31 +5,33 @@
* Use of this source code is governed by an MIT-style license that can be * 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 * found in the LICENSE file at https://angular.io/license
*/ */
import {dirname} from 'canonical-path';
import MagicString from 'magic-string'; import MagicString from 'magic-string';
import * as ts from 'typescript'; import * as ts from 'typescript';
import {AbsoluteFsPath} from '../../../src/ngtsc/path'; import {AbsoluteFsPath} from '../../../src/ngtsc/path';
import {DecorationAnalyzer} from '../../src/analysis/decoration_analyzer'; import {DecorationAnalyzer} from '../../src/analysis/decoration_analyzer';
import {NgccReferencesRegistry} from '../../src/analysis/ngcc_references_registry'; import {NgccReferencesRegistry} from '../../src/analysis/ngcc_references_registry';
import {SwitchMarkerAnalyzer} from '../../src/analysis/switch_marker_analyzer'; import {SwitchMarkerAnalyzer} from '../../src/analysis/switch_marker_analyzer';
import {NodeJSFileSystem} from '../../src/file_system/node_js_file_system';
import {Esm2015ReflectionHost} from '../../src/host/esm2015_host'; import {Esm2015ReflectionHost} from '../../src/host/esm2015_host';
import {EsmRenderer} from '../../src/rendering/esm_renderer'; import {EsmRenderer} from '../../src/rendering/esm_renderer';
import {makeTestEntryPointBundle} from '../helpers/utils'; import {makeTestEntryPointBundle} from '../helpers/utils';
import {MockLogger} from '../helpers/mock_logger'; import {MockLogger} from '../helpers/mock_logger';
const _ = AbsoluteFsPath.fromUnchecked;
function setup(file: {name: string, contents: string}) { function setup(file: {name: string, contents: string}) {
const logger = new MockLogger(); const logger = new MockLogger();
const bundle = makeTestEntryPointBundle('es2015', 'esm2015', false, [file]) !; const bundle = makeTestEntryPointBundle('es2015', 'esm2015', false, [file]) !;
const typeChecker = bundle.src.program.getTypeChecker(); const typeChecker = bundle.src.program.getTypeChecker();
const host = new Esm2015ReflectionHost(logger, false, typeChecker); const host = new Esm2015ReflectionHost(logger, false, typeChecker);
const referencesRegistry = new NgccReferencesRegistry(host); const referencesRegistry = new NgccReferencesRegistry(host);
const decorationAnalyses = const fs = new NodeJSFileSystem();
new DecorationAnalyzer( const decorationAnalyses = new DecorationAnalyzer(
bundle.src.program, bundle.src.options, bundle.src.host, typeChecker, host, fs, bundle.src.program, bundle.src.options, bundle.src.host,
referencesRegistry, [AbsoluteFsPath.fromUnchecked('/')], false) typeChecker, host, referencesRegistry, [_('/')], false)
.analyzeProgram(); .analyzeProgram();
const switchMarkerAnalyses = new SwitchMarkerAnalyzer(host).analyzeProgram(bundle.src.program); const switchMarkerAnalyses = new SwitchMarkerAnalyzer(host).analyzeProgram(bundle.src.program);
const renderer = new EsmRenderer(logger, host, false, bundle); const renderer = new EsmRenderer(fs, logger, host, false, bundle);
return { return {
host, host,
program: bundle.src.program, program: bundle.src.program,
@ -136,11 +138,11 @@ import * as i1 from '@angular/common';`);
it('should insert the given exports at the end of the source file', () => { it('should insert the given exports at the end of the source file', () => {
const {renderer} = setup(PROGRAM); const {renderer} = setup(PROGRAM);
const output = new MagicString(PROGRAM.contents); const output = new MagicString(PROGRAM.contents);
renderer.addExports(output, PROGRAM.name.replace(/\.js$/, ''), [ renderer.addExports(output, _(PROGRAM.name.replace(/\.js$/, '')), [
{from: '/some/a.js', dtsFrom: '/some/a.d.ts', identifier: 'ComponentA1'}, {from: _('/some/a.js'), dtsFrom: _('/some/a.d.ts'), identifier: 'ComponentA1'},
{from: '/some/a.js', dtsFrom: '/some/a.d.ts', identifier: 'ComponentA2'}, {from: _('/some/a.js'), dtsFrom: _('/some/a.d.ts'), identifier: 'ComponentA2'},
{from: '/some/foo/b.js', dtsFrom: '/some/foo/b.d.ts', identifier: 'ComponentB'}, {from: _('/some/foo/b.js'), dtsFrom: _('/some/foo/b.d.ts'), identifier: 'ComponentB'},
{from: PROGRAM.name, dtsFrom: PROGRAM.name, identifier: 'TopLevelComponent'}, {from: _(PROGRAM.name), dtsFrom: _(PROGRAM.name), identifier: 'TopLevelComponent'},
]); ]);
expect(output.toString()).toContain(` expect(output.toString()).toContain(`
// Some other content // Some other content
@ -153,11 +155,11 @@ export {TopLevelComponent};`);
it('should not insert alias exports in js output', () => { it('should not insert alias exports in js output', () => {
const {renderer} = setup(PROGRAM); const {renderer} = setup(PROGRAM);
const output = new MagicString(PROGRAM.contents); const output = new MagicString(PROGRAM.contents);
renderer.addExports(output, PROGRAM.name.replace(/\.js$/, ''), [ renderer.addExports(output, _(PROGRAM.name.replace(/\.js$/, '')), [
{from: '/some/a.js', alias: 'eComponentA1', identifier: 'ComponentA1'}, {from: _('/some/a.js'), alias: 'eComponentA1', identifier: 'ComponentA1'},
{from: '/some/a.js', alias: 'eComponentA2', identifier: 'ComponentA2'}, {from: _('/some/a.js'), alias: 'eComponentA2', identifier: 'ComponentA2'},
{from: '/some/foo/b.js', alias: 'eComponentB', identifier: 'ComponentB'}, {from: _('/some/foo/b.js'), alias: 'eComponentB', identifier: 'ComponentB'},
{from: PROGRAM.name, alias: 'eTopLevelComponent', identifier: 'TopLevelComponent'}, {from: _(PROGRAM.name), alias: 'eTopLevelComponent', identifier: 'TopLevelComponent'},
]); ]);
const outputString = output.toString(); const outputString = output.toString();
expect(outputString).not.toContain(`{eComponentA1 as ComponentA1}`); expect(outputString).not.toContain(`{eComponentA1 as ComponentA1}`);

View File

@ -5,31 +5,33 @@
* Use of this source code is governed by an MIT-style license that can be * 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 * found in the LICENSE file at https://angular.io/license
*/ */
import {dirname} from 'canonical-path';
import MagicString from 'magic-string'; import MagicString from 'magic-string';
import * as ts from 'typescript'; import * as ts from 'typescript';
import {AbsoluteFsPath} from '../../../src/ngtsc/path'; import {AbsoluteFsPath} from '../../../src/ngtsc/path';
import {DecorationAnalyzer} from '../../src/analysis/decoration_analyzer'; import {DecorationAnalyzer} from '../../src/analysis/decoration_analyzer';
import {NgccReferencesRegistry} from '../../src/analysis/ngcc_references_registry'; import {NgccReferencesRegistry} from '../../src/analysis/ngcc_references_registry';
import {SwitchMarkerAnalyzer} from '../../src/analysis/switch_marker_analyzer'; import {SwitchMarkerAnalyzer} from '../../src/analysis/switch_marker_analyzer';
import {NodeJSFileSystem} from '../../src/file_system/node_js_file_system';
import {Esm5ReflectionHost} from '../../src/host/esm5_host'; import {Esm5ReflectionHost} from '../../src/host/esm5_host';
import {Esm5Renderer} from '../../src/rendering/esm5_renderer'; import {Esm5Renderer} from '../../src/rendering/esm5_renderer';
import {makeTestEntryPointBundle, getDeclaration} from '../helpers/utils'; import {makeTestEntryPointBundle, getDeclaration} from '../helpers/utils';
import {MockLogger} from '../helpers/mock_logger'; import {MockLogger} from '../helpers/mock_logger';
const _ = AbsoluteFsPath.fromUnchecked;
function setup(file: {name: string, contents: string}) { function setup(file: {name: string, contents: string}) {
const logger = new MockLogger(); const logger = new MockLogger();
const bundle = makeTestEntryPointBundle('module', 'esm5', false, [file]); const bundle = makeTestEntryPointBundle('module', 'esm5', false, [file]);
const typeChecker = bundle.src.program.getTypeChecker(); const typeChecker = bundle.src.program.getTypeChecker();
const host = new Esm5ReflectionHost(logger, false, typeChecker); const host = new Esm5ReflectionHost(logger, false, typeChecker);
const referencesRegistry = new NgccReferencesRegistry(host); const referencesRegistry = new NgccReferencesRegistry(host);
const decorationAnalyses = const fs = new NodeJSFileSystem();
new DecorationAnalyzer( const decorationAnalyses = new DecorationAnalyzer(
bundle.src.program, bundle.src.options, bundle.src.host, typeChecker, host, fs, bundle.src.program, bundle.src.options, bundle.src.host,
referencesRegistry, [AbsoluteFsPath.fromUnchecked('/')], false) typeChecker, host, referencesRegistry, [_('/')], false)
.analyzeProgram(); .analyzeProgram();
const switchMarkerAnalyses = new SwitchMarkerAnalyzer(host).analyzeProgram(bundle.src.program); const switchMarkerAnalyses = new SwitchMarkerAnalyzer(host).analyzeProgram(bundle.src.program);
const renderer = new Esm5Renderer(logger, host, false, bundle); const renderer = new Esm5Renderer(fs, logger, host, false, bundle);
return { return {
host, host,
program: bundle.src.program, program: bundle.src.program,
@ -173,11 +175,11 @@ import * as i1 from '@angular/common';`);
it('should insert the given exports at the end of the source file', () => { it('should insert the given exports at the end of the source file', () => {
const {renderer} = setup(PROGRAM); const {renderer} = setup(PROGRAM);
const output = new MagicString(PROGRAM.contents); const output = new MagicString(PROGRAM.contents);
renderer.addExports(output, PROGRAM.name.replace(/\.js$/, ''), [ renderer.addExports(output, _(PROGRAM.name.replace(/\.js$/, '')), [
{from: '/some/a.js', dtsFrom: '/some/a.d.ts', identifier: 'ComponentA1'}, {from: _('/some/a.js'), dtsFrom: _('/some/a.d.ts'), identifier: 'ComponentA1'},
{from: '/some/a.js', dtsFrom: '/some/a.d.ts', identifier: 'ComponentA2'}, {from: _('/some/a.js'), dtsFrom: _('/some/a.d.ts'), identifier: 'ComponentA2'},
{from: '/some/foo/b.js', dtsFrom: '/some/foo/b.d.ts', identifier: 'ComponentB'}, {from: _('/some/foo/b.js'), dtsFrom: _('/some/foo/b.d.ts'), identifier: 'ComponentB'},
{from: PROGRAM.name, dtsFrom: PROGRAM.name, identifier: 'TopLevelComponent'}, {from: _(PROGRAM.name), dtsFrom: _(PROGRAM.name), identifier: 'TopLevelComponent'},
]); ]);
expect(output.toString()).toContain(` expect(output.toString()).toContain(`
export {A, B, C, NoIife, BadIife}; export {A, B, C, NoIife, BadIife};
@ -190,11 +192,11 @@ export {TopLevelComponent};`);
it('should not insert alias exports in js output', () => { it('should not insert alias exports in js output', () => {
const {renderer} = setup(PROGRAM); const {renderer} = setup(PROGRAM);
const output = new MagicString(PROGRAM.contents); const output = new MagicString(PROGRAM.contents);
renderer.addExports(output, PROGRAM.name.replace(/\.js$/, ''), [ renderer.addExports(output, _(PROGRAM.name.replace(/\.js$/, '')), [
{from: '/some/a.js', alias: 'eComponentA1', identifier: 'ComponentA1'}, {from: _('/some/a.js'), alias: 'eComponentA1', identifier: 'ComponentA1'},
{from: '/some/a.js', alias: 'eComponentA2', identifier: 'ComponentA2'}, {from: _('/some/a.js'), alias: 'eComponentA2', identifier: 'ComponentA2'},
{from: '/some/foo/b.js', alias: 'eComponentB', identifier: 'ComponentB'}, {from: _('/some/foo/b.js'), alias: 'eComponentB', identifier: 'ComponentB'},
{from: PROGRAM.name, alias: 'eTopLevelComponent', identifier: 'TopLevelComponent'}, {from: _(PROGRAM.name), alias: 'eTopLevelComponent', identifier: 'TopLevelComponent'},
]); ]);
const outputString = output.toString(); const outputString = output.toString();
expect(outputString).not.toContain(`{eComponentA1 as ComponentA1}`); expect(outputString).not.toContain(`{eComponentA1 as ComponentA1}`);

View File

@ -9,6 +9,7 @@ import * as fs from 'fs';
import MagicString from 'magic-string'; import MagicString from 'magic-string';
import * as ts from 'typescript'; import * as ts from 'typescript';
import {fromObject, generateMapFileComment} from 'convert-source-map'; import {fromObject, generateMapFileComment} from 'convert-source-map';
import {AbsoluteFsPath} from '../../../src/ngtsc/path';
import {CompiledClass, DecorationAnalyzer} from '../../src/analysis/decoration_analyzer'; import {CompiledClass, DecorationAnalyzer} from '../../src/analysis/decoration_analyzer';
import {NgccReferencesRegistry} from '../../src/analysis/ngcc_references_registry'; import {NgccReferencesRegistry} from '../../src/analysis/ngcc_references_registry';
import {ModuleWithProvidersAnalyzer} from '../../src/analysis/module_with_providers_analyzer'; import {ModuleWithProvidersAnalyzer} from '../../src/analysis/module_with_providers_analyzer';
@ -20,11 +21,16 @@ import {EntryPointBundle} from '../../src/packages/entry_point_bundle';
import {makeTestEntryPointBundle} from '../helpers/utils'; import {makeTestEntryPointBundle} from '../helpers/utils';
import {Logger} from '../../src/logging/logger'; import {Logger} from '../../src/logging/logger';
import {MockLogger} from '../helpers/mock_logger'; import {MockLogger} from '../helpers/mock_logger';
import {FileSystem} from '../../src/file_system/file_system';
import {NodeJSFileSystem} from '../../src/file_system/node_js_file_system';
const _ = AbsoluteFsPath.fromUnchecked;
class TestRenderer extends Renderer { class TestRenderer extends Renderer {
constructor( constructor(
logger: Logger, host: Esm2015ReflectionHost, isCore: boolean, bundle: EntryPointBundle) { fs: FileSystem, logger: Logger, host: Esm2015ReflectionHost, isCore: boolean,
super(logger, host, isCore, bundle); bundle: EntryPointBundle) {
super(fs, logger, host, isCore, bundle);
} }
addImports( addImports(
output: MagicString, imports: {specifier: string, qualifier: string}[], sf: ts.SourceFile) { output: MagicString, imports: {specifier: string, qualifier: string}[], sf: ts.SourceFile) {
@ -59,8 +65,9 @@ function createTestRenderer(
const typeChecker = bundle.src.program.getTypeChecker(); const typeChecker = bundle.src.program.getTypeChecker();
const host = new Esm2015ReflectionHost(logger, isCore, typeChecker, bundle.dts); const host = new Esm2015ReflectionHost(logger, isCore, typeChecker, bundle.dts);
const referencesRegistry = new NgccReferencesRegistry(host); const referencesRegistry = new NgccReferencesRegistry(host);
const fs = new NodeJSFileSystem();
const decorationAnalyses = new DecorationAnalyzer( const decorationAnalyses = new DecorationAnalyzer(
bundle.src.program, bundle.src.options, bundle.src.host, fs, bundle.src.program, bundle.src.options, bundle.src.host,
typeChecker, host, referencesRegistry, bundle.rootDirs, isCore) typeChecker, host, referencesRegistry, bundle.rootDirs, isCore)
.analyzeProgram(); .analyzeProgram();
const switchMarkerAnalyses = new SwitchMarkerAnalyzer(host).analyzeProgram(bundle.src.program); const switchMarkerAnalyses = new SwitchMarkerAnalyzer(host).analyzeProgram(bundle.src.program);
@ -68,7 +75,7 @@ function createTestRenderer(
new ModuleWithProvidersAnalyzer(host, referencesRegistry).analyzeProgram(bundle.src.program); new ModuleWithProvidersAnalyzer(host, referencesRegistry).analyzeProgram(bundle.src.program);
const privateDeclarationsAnalyses = const privateDeclarationsAnalyses =
new PrivateDeclarationsAnalyzer(host, referencesRegistry).analyzeProgram(bundle.src.program); new PrivateDeclarationsAnalyzer(host, referencesRegistry).analyzeProgram(bundle.src.program);
const renderer = new TestRenderer(logger, host, isCore, bundle); const renderer = new TestRenderer(fs, logger, host, isCore, bundle);
spyOn(renderer, 'addImports').and.callThrough(); spyOn(renderer, 'addImports').and.callThrough();
spyOn(renderer, 'addDefinitions').and.callThrough(); spyOn(renderer, 'addDefinitions').and.callThrough();
spyOn(renderer, 'removeDecorators').and.callThrough(); spyOn(renderer, 'removeDecorators').and.callThrough();
@ -354,7 +361,7 @@ describe('Renderer', () => {
// Add a mock export to trigger export rendering // Add a mock export to trigger export rendering
privateDeclarationsAnalyses.push( privateDeclarationsAnalyses.push(
{identifier: 'ComponentB', from: '/src/file.js', dtsFrom: '/typings/b.d.ts'}); {identifier: 'ComponentB', from: _('/src/file.js'), dtsFrom: _('/typings/b.d.ts')});
const result = renderer.renderProgram( const result = renderer.renderProgram(
decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses, decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses,

View File

@ -9,10 +9,14 @@
import {existsSync, readFileSync} from 'fs'; import {existsSync, readFileSync} from 'fs';
import * as mockFs from 'mock-fs'; import * as mockFs from 'mock-fs';
import {AbsoluteFsPath} from '../../../src/ngtsc/path';
import {NodeJSFileSystem} from '../../src/file_system/node_js_file_system';
import {EntryPoint} from '../../src/packages/entry_point'; import {EntryPoint} from '../../src/packages/entry_point';
import {EntryPointBundle} from '../../src/packages/entry_point_bundle'; import {EntryPointBundle} from '../../src/packages/entry_point_bundle';
import {InPlaceFileWriter} from '../../src/writing/in_place_file_writer'; import {InPlaceFileWriter} from '../../src/writing/in_place_file_writer';
const _ = AbsoluteFsPath.fromUnchecked;
function createMockFileSystem() { function createMockFileSystem() {
mockFs({ mockFs({
'/package/path': { '/package/path': {
@ -39,12 +43,13 @@ describe('InPlaceFileWriter', () => {
afterEach(restoreRealFileSystem); afterEach(restoreRealFileSystem);
it('should write all the FileInfo to the disk', () => { it('should write all the FileInfo to the disk', () => {
const fileWriter = new InPlaceFileWriter(); const fs = new NodeJSFileSystem();
const fileWriter = new InPlaceFileWriter(fs);
fileWriter.writeBundle({} as EntryPoint, {} as EntryPointBundle, [ fileWriter.writeBundle({} as EntryPoint, {} as EntryPointBundle, [
{path: '/package/path/top-level.js', contents: 'MODIFIED TOP LEVEL'}, {path: _('/package/path/top-level.js'), contents: 'MODIFIED TOP LEVEL'},
{path: '/package/path/folder-1/file-1.js', contents: 'MODIFIED FILE 1'}, {path: _('/package/path/folder-1/file-1.js'), contents: 'MODIFIED FILE 1'},
{path: '/package/path/folder-2/file-4.js', contents: 'MODIFIED FILE 4'}, {path: _('/package/path/folder-2/file-4.js'), contents: 'MODIFIED FILE 4'},
{path: '/package/path/folder-3/file-5.js', contents: 'NEW FILE 5'}, {path: _('/package/path/folder-3/file-5.js'), contents: 'NEW FILE 5'},
]); ]);
expect(readFileSync('/package/path/top-level.js', 'utf8')).toEqual('MODIFIED TOP LEVEL'); expect(readFileSync('/package/path/top-level.js', 'utf8')).toEqual('MODIFIED TOP LEVEL');
expect(readFileSync('/package/path/folder-1/file-1.js', 'utf8')).toEqual('MODIFIED FILE 1'); expect(readFileSync('/package/path/folder-1/file-1.js', 'utf8')).toEqual('MODIFIED FILE 1');
@ -55,12 +60,13 @@ describe('InPlaceFileWriter', () => {
}); });
it('should create backups of all files that previously existed', () => { it('should create backups of all files that previously existed', () => {
const fileWriter = new InPlaceFileWriter(); const fs = new NodeJSFileSystem();
const fileWriter = new InPlaceFileWriter(fs);
fileWriter.writeBundle({} as EntryPoint, {} as EntryPointBundle, [ fileWriter.writeBundle({} as EntryPoint, {} as EntryPointBundle, [
{path: '/package/path/top-level.js', contents: 'MODIFIED TOP LEVEL'}, {path: _('/package/path/top-level.js'), contents: 'MODIFIED TOP LEVEL'},
{path: '/package/path/folder-1/file-1.js', contents: 'MODIFIED FILE 1'}, {path: _('/package/path/folder-1/file-1.js'), contents: 'MODIFIED FILE 1'},
{path: '/package/path/folder-2/file-4.js', contents: 'MODIFIED FILE 4'}, {path: _('/package/path/folder-2/file-4.js'), contents: 'MODIFIED FILE 4'},
{path: '/package/path/folder-3/file-5.js', contents: 'NEW FILE 5'}, {path: _('/package/path/folder-3/file-5.js'), contents: 'NEW FILE 5'},
]); ]);
expect(readFileSync('/package/path/top-level.js.__ivy_ngcc_bak', 'utf8')) expect(readFileSync('/package/path/top-level.js.__ivy_ngcc_bak', 'utf8'))
.toEqual('ORIGINAL TOP LEVEL'); .toEqual('ORIGINAL TOP LEVEL');
@ -74,12 +80,13 @@ describe('InPlaceFileWriter', () => {
}); });
it('should error if the backup file already exists', () => { it('should error if the backup file already exists', () => {
const fileWriter = new InPlaceFileWriter(); const fs = new NodeJSFileSystem();
const fileWriter = new InPlaceFileWriter(fs);
expect( expect(
() => fileWriter.writeBundle( () => fileWriter.writeBundle(
{} as EntryPoint, {} as EntryPointBundle, {} as EntryPoint, {} as EntryPointBundle,
[ [
{path: '/package/path/already-backed-up.js', contents: 'MODIFIED BACKED UP'}, {path: _('/package/path/already-backed-up.js'), contents: 'MODIFIED BACKED UP'},
])) ]))
.toThrowError( .toThrowError(
'Tried to overwrite /package/path/already-backed-up.js.__ivy_ngcc_bak with an ngcc back up file, which is disallowed.'); 'Tried to overwrite /package/path/already-backed-up.js.__ivy_ngcc_bak with an ngcc back up file, which is disallowed.');

View File

@ -10,6 +10,8 @@ import {existsSync, readFileSync} from 'fs';
import * as mockFs from 'mock-fs'; import * as mockFs from 'mock-fs';
import {AbsoluteFsPath} from '../../../src/ngtsc/path'; import {AbsoluteFsPath} from '../../../src/ngtsc/path';
import {FileSystem} from '../../src/file_system/file_system';
import {NodeJSFileSystem} from '../../src/file_system/node_js_file_system';
import {EntryPoint, EntryPointFormat, EntryPointJsonProperty, getEntryPointInfo} from '../../src/packages/entry_point'; import {EntryPoint, EntryPointFormat, EntryPointJsonProperty, getEntryPointInfo} from '../../src/packages/entry_point';
import {EntryPointBundle, makeEntryPointBundle} from '../../src/packages/entry_point_bundle'; import {EntryPointBundle, makeEntryPointBundle} from '../../src/packages/entry_point_bundle';
import {FileWriter} from '../../src/writing/file_writer'; import {FileWriter} from '../../src/writing/file_writer';
@ -81,6 +83,7 @@ describe('NewEntryPointFileWriter', () => {
beforeEach(createMockFileSystem); beforeEach(createMockFileSystem);
afterEach(restoreRealFileSystem); afterEach(restoreRealFileSystem);
let fs: FileSystem;
let fileWriter: FileWriter; let fileWriter: FileWriter;
let entryPoint: EntryPoint; let entryPoint: EntryPoint;
let esm5bundle: EntryPointBundle; let esm5bundle: EntryPointBundle;
@ -88,17 +91,21 @@ describe('NewEntryPointFileWriter', () => {
describe('writeBundle() [primary entry-point]', () => { describe('writeBundle() [primary entry-point]', () => {
beforeEach(() => { beforeEach(() => {
fileWriter = new NewEntryPointFileWriter(); fs = new NodeJSFileSystem();
entryPoint = fileWriter = new NewEntryPointFileWriter(fs);
getEntryPointInfo(new MockLogger(), _('/node_modules/test'), _('/node_modules/test')) !; entryPoint = getEntryPointInfo(
esm5bundle = makeTestBundle(entryPoint, 'module', 'esm5'); fs, new MockLogger(), _('/node_modules/test'), _('/node_modules/test')) !;
esm2015bundle = makeTestBundle(entryPoint, 'es2015', 'esm2015'); esm5bundle = makeTestBundle(fs, entryPoint, 'module', 'esm5');
esm2015bundle = makeTestBundle(fs, entryPoint, 'es2015', 'esm2015');
}); });
it('should write the modified files to a new folder', () => { it('should write the modified files to a new folder', () => {
fileWriter.writeBundle(entryPoint, esm5bundle, [ fileWriter.writeBundle(entryPoint, esm5bundle, [
{path: '/node_modules/test/esm5.js', contents: 'export function FooTop() {} // MODIFIED'}, {
{path: '/node_modules/test/esm5.js.map', contents: 'MODIFIED MAPPING DATA'}, path: _('/node_modules/test/esm5.js'),
contents: 'export function FooTop() {} // MODIFIED'
},
{path: _('/node_modules/test/esm5.js.map'), contents: 'MODIFIED MAPPING DATA'},
]); ]);
expect(readFileSync('/node_modules/test/__ivy_ngcc__/esm5.js', 'utf8')) expect(readFileSync('/node_modules/test/__ivy_ngcc__/esm5.js', 'utf8'))
.toEqual('export function FooTop() {} // MODIFIED'); .toEqual('export function FooTop() {} // MODIFIED');
@ -112,7 +119,10 @@ describe('NewEntryPointFileWriter', () => {
it('should also copy unmodified files in the program', () => { it('should also copy unmodified files in the program', () => {
fileWriter.writeBundle(entryPoint, esm2015bundle, [ fileWriter.writeBundle(entryPoint, esm2015bundle, [
{path: '/node_modules/test/es2015/foo.js', contents: 'export class FooTop {} // MODIFIED'}, {
path: _('/node_modules/test/es2015/foo.js'),
contents: 'export class FooTop {} // MODIFIED'
},
]); ]);
expect(readFileSync('/node_modules/test/__ivy_ngcc__/es2015/foo.js', 'utf8')) expect(readFileSync('/node_modules/test/__ivy_ngcc__/es2015/foo.js', 'utf8'))
.toEqual('export class FooTop {} // MODIFIED'); .toEqual('export class FooTop {} // MODIFIED');
@ -126,14 +136,20 @@ describe('NewEntryPointFileWriter', () => {
it('should update the package.json properties', () => { it('should update the package.json properties', () => {
fileWriter.writeBundle(entryPoint, esm5bundle, [ fileWriter.writeBundle(entryPoint, esm5bundle, [
{path: '/node_modules/test/esm5.js', contents: 'export function FooTop() {} // MODIFIED'}, {
path: _('/node_modules/test/esm5.js'),
contents: 'export function FooTop() {} // MODIFIED'
},
]); ]);
expect(loadPackageJson('/node_modules/test')).toEqual(jasmine.objectContaining({ expect(loadPackageJson('/node_modules/test')).toEqual(jasmine.objectContaining({
module_ivy_ngcc: '__ivy_ngcc__/esm5.js', module_ivy_ngcc: '__ivy_ngcc__/esm5.js',
})); }));
fileWriter.writeBundle(entryPoint, esm2015bundle, [ fileWriter.writeBundle(entryPoint, esm2015bundle, [
{path: '/node_modules/test/es2015/foo.js', contents: 'export class FooTop {} // MODIFIED'}, {
path: _('/node_modules/test/es2015/foo.js'),
contents: 'export class FooTop {} // MODIFIED'
},
]); ]);
expect(loadPackageJson('/node_modules/test')).toEqual(jasmine.objectContaining({ expect(loadPackageJson('/node_modules/test')).toEqual(jasmine.objectContaining({
module_ivy_ngcc: '__ivy_ngcc__/esm5.js', module_ivy_ngcc: '__ivy_ngcc__/esm5.js',
@ -144,10 +160,10 @@ describe('NewEntryPointFileWriter', () => {
it('should overwrite and backup typings files', () => { it('should overwrite and backup typings files', () => {
fileWriter.writeBundle(entryPoint, esm2015bundle, [ fileWriter.writeBundle(entryPoint, esm2015bundle, [
{ {
path: '/node_modules/test/index.d.ts', path: _('/node_modules/test/index.d.ts'),
contents: 'export declare class FooTop {} // MODIFIED' contents: 'export declare class FooTop {} // MODIFIED',
}, },
{path: '/node_modules/test/index.d.ts.map', contents: 'MODIFIED MAPPING DATA'}, {path: _('/node_modules/test/index.d.ts.map'), contents: 'MODIFIED MAPPING DATA'},
]); ]);
expect(readFileSync('/node_modules/test/index.d.ts', 'utf8')) expect(readFileSync('/node_modules/test/index.d.ts', 'utf8'))
.toEqual('export declare class FooTop {} // MODIFIED'); .toEqual('export declare class FooTop {} // MODIFIED');
@ -165,16 +181,20 @@ describe('NewEntryPointFileWriter', () => {
describe('writeBundle() [secondary entry-point]', () => { describe('writeBundle() [secondary entry-point]', () => {
beforeEach(() => { beforeEach(() => {
fileWriter = new NewEntryPointFileWriter(); fs = new NodeJSFileSystem();
entryPoint = fileWriter = new NewEntryPointFileWriter(fs);
getEntryPointInfo(new MockLogger(), _('/node_modules/test'), _('/node_modules/test/a')) !; entryPoint = getEntryPointInfo(
esm5bundle = makeTestBundle(entryPoint, 'module', 'esm5'); fs, new MockLogger(), _('/node_modules/test'), _('/node_modules/test/a')) !;
esm2015bundle = makeTestBundle(entryPoint, 'es2015', 'esm2015'); esm5bundle = makeTestBundle(fs, entryPoint, 'module', 'esm5');
esm2015bundle = makeTestBundle(fs, entryPoint, 'es2015', 'esm2015');
}); });
it('should write the modified file to a new folder', () => { it('should write the modified file to a new folder', () => {
fileWriter.writeBundle(entryPoint, esm5bundle, [ fileWriter.writeBundle(entryPoint, esm5bundle, [
{path: '/node_modules/test/a/esm5.js', contents: 'export function FooA() {} // MODIFIED'}, {
path: _('/node_modules/test/a/esm5.js'),
contents: 'export function FooA() {} // MODIFIED'
},
]); ]);
expect(readFileSync('/node_modules/test/__ivy_ngcc__/a/esm5.js', 'utf8')) expect(readFileSync('/node_modules/test/__ivy_ngcc__/a/esm5.js', 'utf8'))
.toEqual('export function FooA() {} // MODIFIED'); .toEqual('export function FooA() {} // MODIFIED');
@ -184,7 +204,10 @@ describe('NewEntryPointFileWriter', () => {
it('should also copy unmodified files in the program', () => { it('should also copy unmodified files in the program', () => {
fileWriter.writeBundle(entryPoint, esm2015bundle, [ fileWriter.writeBundle(entryPoint, esm2015bundle, [
{path: '/node_modules/test/a/es2015/foo.js', contents: 'export class FooA {} // MODIFIED'}, {
path: _('/node_modules/test/a/es2015/foo.js'),
contents: 'export class FooA {} // MODIFIED'
},
]); ]);
expect(readFileSync('/node_modules/test/__ivy_ngcc__/a/es2015/foo.js', 'utf8')) expect(readFileSync('/node_modules/test/__ivy_ngcc__/a/es2015/foo.js', 'utf8'))
.toEqual('export class FooA {} // MODIFIED'); .toEqual('export class FooA {} // MODIFIED');
@ -198,14 +221,20 @@ describe('NewEntryPointFileWriter', () => {
it('should update the package.json properties', () => { it('should update the package.json properties', () => {
fileWriter.writeBundle(entryPoint, esm5bundle, [ fileWriter.writeBundle(entryPoint, esm5bundle, [
{path: '/node_modules/test/a/esm5.js', contents: 'export function FooA() {} // MODIFIED'}, {
path: _('/node_modules/test/a/esm5.js'),
contents: 'export function FooA() {} // MODIFIED'
},
]); ]);
expect(loadPackageJson('/node_modules/test/a')).toEqual(jasmine.objectContaining({ expect(loadPackageJson('/node_modules/test/a')).toEqual(jasmine.objectContaining({
module_ivy_ngcc: '../__ivy_ngcc__/a/esm5.js', module_ivy_ngcc: '../__ivy_ngcc__/a/esm5.js',
})); }));
fileWriter.writeBundle(entryPoint, esm2015bundle, [ fileWriter.writeBundle(entryPoint, esm2015bundle, [
{path: '/node_modules/test/a/es2015/foo.js', contents: 'export class FooA {} // MODIFIED'}, {
path: _('/node_modules/test/a/es2015/foo.js'),
contents: 'export class FooA {} // MODIFIED'
},
]); ]);
expect(loadPackageJson('/node_modules/test/a')).toEqual(jasmine.objectContaining({ expect(loadPackageJson('/node_modules/test/a')).toEqual(jasmine.objectContaining({
module_ivy_ngcc: '../__ivy_ngcc__/a/esm5.js', module_ivy_ngcc: '../__ivy_ngcc__/a/esm5.js',
@ -216,7 +245,7 @@ describe('NewEntryPointFileWriter', () => {
it('should overwrite and backup typings files', () => { it('should overwrite and backup typings files', () => {
fileWriter.writeBundle(entryPoint, esm2015bundle, [ fileWriter.writeBundle(entryPoint, esm2015bundle, [
{ {
path: '/node_modules/test/a/index.d.ts', path: _('/node_modules/test/a/index.d.ts'),
contents: 'export declare class FooA {} // MODIFIED' contents: 'export declare class FooA {} // MODIFIED'
}, },
]); ]);
@ -230,16 +259,20 @@ describe('NewEntryPointFileWriter', () => {
describe('writeBundle() [entry-point (with files placed outside entry-point folder)]', () => { describe('writeBundle() [entry-point (with files placed outside entry-point folder)]', () => {
beforeEach(() => { beforeEach(() => {
fileWriter = new NewEntryPointFileWriter(); fs = new NodeJSFileSystem();
entryPoint = fileWriter = new NewEntryPointFileWriter(fs);
getEntryPointInfo(new MockLogger(), _('/node_modules/test'), _('/node_modules/test/b')) !; entryPoint = getEntryPointInfo(
esm5bundle = makeTestBundle(entryPoint, 'module', 'esm5'); fs, new MockLogger(), _('/node_modules/test'), _('/node_modules/test/b')) !;
esm2015bundle = makeTestBundle(entryPoint, 'es2015', 'esm2015'); esm5bundle = makeTestBundle(fs, entryPoint, 'module', 'esm5');
esm2015bundle = makeTestBundle(fs, entryPoint, 'es2015', 'esm2015');
}); });
it('should write the modified file to a new folder', () => { it('should write the modified file to a new folder', () => {
fileWriter.writeBundle(entryPoint, esm5bundle, [ fileWriter.writeBundle(entryPoint, esm5bundle, [
{path: '/node_modules/test/lib/esm5.js', contents: 'export function FooB() {} // MODIFIED'}, {
path: _('/node_modules/test/lib/esm5.js'),
contents: 'export function FooB() {} // MODIFIED'
},
]); ]);
expect(readFileSync('/node_modules/test/__ivy_ngcc__/lib/esm5.js', 'utf8')) expect(readFileSync('/node_modules/test/__ivy_ngcc__/lib/esm5.js', 'utf8'))
.toEqual('export function FooB() {} // MODIFIED'); .toEqual('export function FooB() {} // MODIFIED');
@ -250,7 +283,7 @@ describe('NewEntryPointFileWriter', () => {
it('should also copy unmodified files in the program', () => { it('should also copy unmodified files in the program', () => {
fileWriter.writeBundle(entryPoint, esm2015bundle, [ fileWriter.writeBundle(entryPoint, esm2015bundle, [
{ {
path: '/node_modules/test/lib/es2015/foo.js', path: _('/node_modules/test/lib/es2015/foo.js'),
contents: 'export class FooB {} // MODIFIED' contents: 'export class FooB {} // MODIFIED'
}, },
]); ]);
@ -278,7 +311,7 @@ describe('NewEntryPointFileWriter', () => {
it('should not copy files outside of the package', () => { it('should not copy files outside of the package', () => {
fileWriter.writeBundle(entryPoint, esm2015bundle, [ fileWriter.writeBundle(entryPoint, esm2015bundle, [
{ {
path: '/node_modules/test/lib/es2015/foo.js', path: _('/node_modules/test/lib/es2015/foo.js'),
contents: 'export class FooB {} // MODIFIED' contents: 'export class FooB {} // MODIFIED'
}, },
]); ]);
@ -288,7 +321,10 @@ describe('NewEntryPointFileWriter', () => {
it('should update the package.json properties', () => { it('should update the package.json properties', () => {
fileWriter.writeBundle(entryPoint, esm5bundle, [ fileWriter.writeBundle(entryPoint, esm5bundle, [
{path: '/node_modules/test/lib/esm5.js', contents: 'export function FooB() {} // MODIFIED'}, {
path: _('/node_modules/test/lib/esm5.js'),
contents: 'export function FooB() {} // MODIFIED'
},
]); ]);
expect(loadPackageJson('/node_modules/test/b')).toEqual(jasmine.objectContaining({ expect(loadPackageJson('/node_modules/test/b')).toEqual(jasmine.objectContaining({
module_ivy_ngcc: '../__ivy_ngcc__/lib/esm5.js', module_ivy_ngcc: '../__ivy_ngcc__/lib/esm5.js',
@ -296,7 +332,7 @@ describe('NewEntryPointFileWriter', () => {
fileWriter.writeBundle(entryPoint, esm2015bundle, [ fileWriter.writeBundle(entryPoint, esm2015bundle, [
{ {
path: '/node_modules/test/lib/es2015/foo.js', path: _('/node_modules/test/lib/es2015/foo.js'),
contents: 'export class FooB {} // MODIFIED' contents: 'export class FooB {} // MODIFIED'
}, },
]); ]);
@ -309,7 +345,7 @@ describe('NewEntryPointFileWriter', () => {
it('should overwrite and backup typings files', () => { it('should overwrite and backup typings files', () => {
fileWriter.writeBundle(entryPoint, esm2015bundle, [ fileWriter.writeBundle(entryPoint, esm2015bundle, [
{ {
path: '/node_modules/test/typings/index.d.ts', path: _('/node_modules/test/typings/index.d.ts'),
contents: 'export declare class FooB {} // MODIFIED' contents: 'export declare class FooB {} // MODIFIED'
}, },
]); ]);
@ -323,9 +359,9 @@ describe('NewEntryPointFileWriter', () => {
}); });
function makeTestBundle( function makeTestBundle(
entryPoint: EntryPoint, formatProperty: EntryPointJsonProperty, fs: FileSystem, entryPoint: EntryPoint, formatProperty: EntryPointJsonProperty,
format: EntryPointFormat): EntryPointBundle { format: EntryPointFormat): EntryPointBundle {
return makeEntryPointBundle( return makeEntryPointBundle(
entryPoint.path, entryPoint.packageJson[formatProperty] !, entryPoint.typings, false, fs, entryPoint.path, entryPoint.packageJson[formatProperty] !, entryPoint.typings, false,
formatProperty, format, true) !; formatProperty, format, true) !;
} }