From 16d7dde2ad89eb4610c74da63e0e4b00afb8b577 Mon Sep 17 00:00:00 2001 From: Pete Bacon Darwin Date: Sun, 28 Apr 2019 20:47:57 +0100 Subject: [PATCH] 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 --- .../ngcc/src/analysis/decoration_analyzer.ts | 16 +-- .../analysis/private_declarations_analyzer.ts | 10 +- .../src/dependencies/esm_dependency_host.ts | 8 +- .../ngcc/src/dependencies/module_resolver.ts | 20 ++-- .../ngcc/src/file_system/file_system.ts | 38 ++++++ .../src/file_system/node_js_file_system.ts | 30 +++++ packages/compiler-cli/ngcc/src/main.ts | 56 +++++---- .../ngcc/src/packages/build_marker.ts | 8 +- .../ngcc/src/packages/bundle_program.ts | 30 ++--- .../ngcc/src/packages/entry_point.ts | 27 ++--- .../ngcc/src/packages/entry_point_bundle.ts | 24 ++-- .../ngcc/src/packages/entry_point_finder.ts | 38 +++--- .../ngcc/src/packages/ngcc_compiler_host.ts | 66 +++++++++++ .../ngcc/src/packages/transformer.ts | 11 +- .../ngcc/src/rendering/esm5_renderer.ts | 15 ++- .../ngcc/src/rendering/esm_renderer.ts | 24 ++-- .../ngcc/src/rendering/renderer.ts | 59 +++++----- .../ngcc/src/writing/in_place_file_writer.ts | 23 ++-- .../writing/new_entry_point_file_writer.ts | 39 ++++--- .../test/analysis/decoration_analyzer_spec.ts | 4 +- .../private_declarations_analyzer_spec.ts | 11 +- .../analysis/switch_marker_analyzer_spec.ts | 2 - .../dependencies/dependency_resolver_spec.ts | 4 +- .../dependencies/esm_dependency_host_spec.ts | 21 ++-- .../test/dependencies/module_resolver_spec.ts | 47 +++++--- .../compiler-cli/ngcc/test/helpers/utils.ts | 11 +- .../ngcc/test/host/esm2015_host_spec.ts | 12 +- .../ngcc/test/integration/ngcc_spec.ts | 7 +- .../ngcc/test/packages/build_marker_spec.ts | 13 ++- .../test/packages/entry_point_finder_spec.ts | 6 +- .../ngcc/test/packages/entry_point_spec.ts | 35 +++--- .../test/rendering/esm2015_renderer_spec.ts | 36 +++--- .../ngcc/test/rendering/esm5_renderer_spec.ts | 36 +++--- .../ngcc/test/rendering/renderer_spec.ts | 17 ++- .../test/writing/in_place_file_writer_spec.ts | 31 +++-- .../new_entry_point_file_writer_spec.ts | 108 ++++++++++++------ 36 files changed, 590 insertions(+), 353 deletions(-) create mode 100644 packages/compiler-cli/ngcc/src/file_system/file_system.ts create mode 100644 packages/compiler-cli/ngcc/src/file_system/node_js_file_system.ts create mode 100644 packages/compiler-cli/ngcc/src/packages/ngcc_compiler_host.ts diff --git a/packages/compiler-cli/ngcc/src/analysis/decoration_analyzer.ts b/packages/compiler-cli/ngcc/src/analysis/decoration_analyzer.ts index 65f491f070..b69cb2eeab 100644 --- a/packages/compiler-cli/ngcc/src/analysis/decoration_analyzer.ts +++ b/packages/compiler-cli/ngcc/src/analysis/decoration_analyzer.ts @@ -6,9 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ import {ConstantPool} from '@angular/compiler'; -import {NOOP_PERF_RECORDER} from '@angular/compiler-cli/src/ngtsc/perf'; -import * as path from 'canonical-path'; -import * as fs from 'fs'; +import * as path from 'path'; import * as ts from 'typescript'; 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 {LocalModuleScopeRegistry, MetadataDtsModuleScopeResolver} from '../../../src/ngtsc/scope'; import {CompileResult, DecoratorHandler, DetectResult, HandlerPrecedence} from '../../../src/ngtsc/transform'; +import {FileSystem} from '../file_system/file_system'; import {DecoratedClass} from '../host/decorated_class'; import {NgccReflectionHost} from '../host/ngcc_host'; import {isDefined} from '../utils'; @@ -53,9 +52,10 @@ export interface MatchingHandler { * Simple class that resolves and loads files directly from the filesystem. */ class NgccResourceLoader implements ResourceLoader { + constructor(private fs: FileSystem) {} canPreload = false; preload(): undefined|Promise { 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 { 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. */ export class DecorationAnalyzer { - resourceManager = new NgccResourceLoader(); + resourceManager = new NgccResourceLoader(this.fs); metaRegistry = new LocalMetadataRegistry(); dtsMetaReader = new DtsMetadataReader(this.typeChecker, this.reflectionHost); fullMetaReader = new CompoundMetadataReader([this.metaRegistry, this.dtsMetaReader]); @@ -73,8 +73,8 @@ export class DecorationAnalyzer { new LocalIdentifierStrategy(), 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 - // projects only ever have one rootDir. Instead, ngcc should just switch its emitted imort based - // on whether a bestGuessOwningModule is present in the Reference. + // projects only ever have one rootDir. Instead, ngcc should just switch its emitted import + // based on whether a bestGuessOwningModule is present in the Reference. new LogicalProjectStrategy(this.typeChecker, new LogicalFileSystem(this.rootDirs)), ]); dtsModuleScopeResolver = @@ -110,7 +110,7 @@ export class DecorationAnalyzer { ]; 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 reflectionHost: NgccReflectionHost, private referencesRegistry: ReferencesRegistry, private rootDirs: AbsoluteFsPath[], private isCore: boolean) {} diff --git a/packages/compiler-cli/ngcc/src/analysis/private_declarations_analyzer.ts b/packages/compiler-cli/ngcc/src/analysis/private_declarations_analyzer.ts index ff7d3ce2cc..72698b5582 100644 --- a/packages/compiler-cli/ngcc/src/analysis/private_declarations_analyzer.ts +++ b/packages/compiler-cli/ngcc/src/analysis/private_declarations_analyzer.ts @@ -7,6 +7,7 @@ */ import * as ts from 'typescript'; +import {AbsoluteFsPath} from '../../../src/ngtsc/path'; import {Declaration} from '../../../src/ngtsc/reflection'; import {NgccReflectionHost} from '../host/ngcc_host'; import {hasNameIdentifier, isDefined} from '../utils'; @@ -14,8 +15,8 @@ import {NgccReferencesRegistry} from './ngcc_references_registry'; export interface ExportInfo { identifier: string; - from: string; - dtsFrom?: string|null; + from: AbsoluteFsPath; + dtsFrom?: AbsoluteFsPath|null; alias?: string|null; } export type PrivateDeclarationsAnalyses = ExportInfo[]; @@ -93,11 +94,12 @@ export class PrivateDeclarationsAnalyzer { }); return Array.from(privateDeclarations.keys()).map(id => { - const from = id.getSourceFile().fileName; + const from = AbsoluteFsPath.fromSourceFile(id.getSourceFile()); const declaration = privateDeclarations.get(id) !; const alias = exportAliasDeclarations.get(id) || null; 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}; }); diff --git a/packages/compiler-cli/ngcc/src/dependencies/esm_dependency_host.ts b/packages/compiler-cli/ngcc/src/dependencies/esm_dependency_host.ts index b71663f167..33c7630698 100644 --- a/packages/compiler-cli/ngcc/src/dependencies/esm_dependency_host.ts +++ b/packages/compiler-cli/ngcc/src/dependencies/esm_dependency_host.ts @@ -5,12 +5,10 @@ * 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 * as ts from 'typescript'; import {AbsoluteFsPath} from '../../../src/ngtsc/path'; - +import {FileSystem} from '../file_system/file_system'; import {DependencyHost, DependencyInfo} from './dependency_host'; import {ModuleResolver, ResolvedDeepImport, ResolvedRelativeModule} from './module_resolver'; @@ -19,7 +17,7 @@ import {ModuleResolver, ResolvedDeepImport, ResolvedRelativeModule} from './modu * Helper functions for computing dependencies. */ 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. @@ -54,7 +52,7 @@ export class EsmDependencyHost implements DependencyHost { private recursivelyFindDependencies( file: AbsoluteFsPath, dependencies: Set, missing: Set, deepImports: Set, alreadySeen: Set): void { - const fromContents = fs.readFileSync(file, 'utf8'); + const fromContents = this.fs.readFile(file); if (!this.hasImportOrReexportStatements(fromContents)) { return; } diff --git a/packages/compiler-cli/ngcc/src/dependencies/module_resolver.ts b/packages/compiler-cli/ngcc/src/dependencies/module_resolver.ts index 1f3e4e704b..e8c12e361d 100644 --- a/packages/compiler-cli/ngcc/src/dependencies/module_resolver.ts +++ b/packages/compiler-cli/ngcc/src/dependencies/module_resolver.ts @@ -6,12 +6,10 @@ * found in the LICENSE file at https://angular.io/license */ -import * as fs from 'fs'; - import {AbsoluteFsPath} from '../../../src/ngtsc/path'; +import {FileSystem} from '../file_system/file_system'; import {PathMappings, isRelativePath} from '../utils'; - /** * 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 { 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) : []; } @@ -139,11 +139,11 @@ export class ModuleResolver { * 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. */ - private resolvePath(path: string, postFixes: string[]): AbsoluteFsPath|null { + private resolvePath(path: AbsoluteFsPath, postFixes: string[]): AbsoluteFsPath|null { for (const postFix of postFixes) { - const testPath = path + postFix; - if (fs.existsSync(testPath)) { - return AbsoluteFsPath.from(testPath); + const testPath = AbsoluteFsPath.fromUnchecked(path + postFix); + if (this.fs.exists(testPath)) { + return testPath; } } return null; @@ -155,7 +155,7 @@ export class ModuleResolver { * This is achieved by checking for the existence of `${modulePath}/package.json`. */ 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; while (folder !== '/') { folder = AbsoluteFsPath.dirname(folder); - if (fs.existsSync(AbsoluteFsPath.join(folder, 'package.json'))) { + if (this.fs.exists(AbsoluteFsPath.join(folder, 'package.json'))) { return folder; } } diff --git a/packages/compiler-cli/ngcc/src/file_system/file_system.ts b/packages/compiler-cli/ngcc/src/file_system/file_system.ts new file mode 100644 index 0000000000..9c9653b48b --- /dev/null +++ b/packages/compiler-cli/ngcc/src/file_system/file_system.ts @@ -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; +} diff --git a/packages/compiler-cli/ngcc/src/file_system/node_js_file_system.ts b/packages/compiler-cli/ngcc/src/file_system/node_js_file_system.ts new file mode 100644 index 0000000000..7f821c3986 --- /dev/null +++ b/packages/compiler-cli/ngcc/src/file_system/node_js_file_system.ts @@ -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); } +} diff --git a/packages/compiler-cli/ngcc/src/main.ts b/packages/compiler-cli/ngcc/src/main.ts index 8ed2a2faf2..c69424fa91 100644 --- a/packages/compiler-cli/ngcc/src/main.ts +++ b/packages/compiler-cli/ngcc/src/main.ts @@ -5,15 +5,13 @@ * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ - -import {resolve} from 'canonical-path'; -import {readFileSync} from 'fs'; - import {AbsoluteFsPath} from '../../src/ngtsc/path'; import {DependencyResolver} from './dependencies/dependency_resolver'; import {EsmDependencyHost} from './dependencies/esm_dependency_host'; 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 {Logger} from './logging/logger'; 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 {NewEntryPointFileWriter} from './writing/new_entry_point_file_writer'; - - /** * The options to configure the ngcc compiler. */ @@ -80,20 +76,20 @@ export function mainNgcc( {basePath, targetEntryPointPath, propertiesToConsider = SUPPORTED_FORMAT_PROPERTIES, compileAllFormats = true, createNewEntryPointFormats = false, logger = new ConsoleLogger(LogLevel.info), pathMappings}: NgccOptions): void { - const transformer = new Transformer(logger); - const moduleResolver = new ModuleResolver(pathMappings); - const host = new EsmDependencyHost(moduleResolver); + const fs = new NodeJSFileSystem(); + const transformer = new Transformer(fs, logger); + const moduleResolver = new ModuleResolver(fs, pathMappings); + const host = new EsmDependencyHost(fs, moduleResolver); const resolver = new DependencyResolver(logger, host); - const finder = new EntryPointFinder(logger, resolver); - const fileWriter = getFileWriter(createNewEntryPointFormats); + const finder = new EntryPointFinder(fs, logger, resolver); + const fileWriter = getFileWriter(fs, createNewEntryPointFormats); - const absoluteTargetEntryPointPath = targetEntryPointPath ? - AbsoluteFsPath.from(resolve(basePath, targetEntryPointPath)) : - undefined; + const absoluteTargetEntryPointPath = + targetEntryPointPath ? AbsoluteFsPath.resolve(basePath, targetEntryPointPath) : undefined; if (absoluteTargetEntryPointPath && hasProcessedTargetEntryPoint( - absoluteTargetEntryPointPath, propertiesToConsider, compileAllFormats)) { + fs, absoluteTargetEntryPointPath, propertiesToConsider, compileAllFormats)) { logger.info('The target entry-point has already been processed'); return; } @@ -102,7 +98,7 @@ export function mainNgcc( AbsoluteFsPath.from(basePath), absoluteTargetEntryPointPath, pathMappings); if (absoluteTargetEntryPointPath && entryPoints.length === 0) { - markNonAngularPackageAsProcessed(absoluteTargetEntryPointPath, propertiesToConsider); + markNonAngularPackageAsProcessed(fs, absoluteTargetEntryPointPath, propertiesToConsider); return; } @@ -138,8 +134,8 @@ export function mainNgcc( // the property as processed even if its underlying format has been built already. if (!compiledFormats.has(formatPath) && (compileAllFormats || isFirstFormat)) { const bundle = makeEntryPointBundle( - entryPoint.path, formatPath, entryPoint.typings, isCore, property, format, processDts, - pathMappings); + fs, entryPoint.path, formatPath, entryPoint.typings, isCore, property, format, + processDts, pathMappings); if (bundle) { logger.info(`Compiling ${entryPoint.name} : ${property} as ${format}`); 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 // previous property. if (compiledFormats.has(formatPath)) { - markAsProcessed(entryPointPackageJson, entryPointPackageJsonPath, property); + markAsProcessed(fs, entryPointPackageJson, entryPointPackageJsonPath, property); if (processDts) { - markAsProcessed(entryPointPackageJson, entryPointPackageJsonPath, 'typings'); + markAsProcessed(fs, entryPointPackageJson, entryPointPackageJsonPath, 'typings'); } } } @@ -170,14 +166,15 @@ export function mainNgcc( }); } -function getFileWriter(createNewEntryPointFormats: boolean): FileWriter { - return createNewEntryPointFormats ? new NewEntryPointFileWriter() : new InPlaceFileWriter(); +function getFileWriter(fs: FileSystem, createNewEntryPointFormats: boolean): FileWriter { + return createNewEntryPointFormats ? new NewEntryPointFileWriter(fs) : new InPlaceFileWriter(fs); } function hasProcessedTargetEntryPoint( - targetPath: AbsoluteFsPath, propertiesToConsider: string[], compileAllFormats: boolean) { - const packageJsonPath = AbsoluteFsPath.from(resolve(targetPath, 'package.json')); - const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8')); + fs: FileSystem, targetPath: AbsoluteFsPath, propertiesToConsider: string[], + compileAllFormats: boolean) { + const packageJsonPath = AbsoluteFsPath.resolve(targetPath, 'package.json'); + const packageJson = JSON.parse(fs.readFile(packageJsonPath)); for (const property of propertiesToConsider) { 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 * triggering ngcc for this entry-point in the future. */ -function markNonAngularPackageAsProcessed(path: AbsoluteFsPath, propertiesToConsider: string[]) { - const packageJsonPath = AbsoluteFsPath.from(resolve(path, 'package.json')); - const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8')); +function markNonAngularPackageAsProcessed( + fs: FileSystem, path: AbsoluteFsPath, propertiesToConsider: string[]) { + const packageJsonPath = AbsoluteFsPath.resolve(path, 'package.json'); + const packageJson = JSON.parse(fs.readFile(packageJsonPath)); propertiesToConsider.forEach(formatProperty => { if (packageJson[formatProperty]) - markAsProcessed(packageJson, packageJsonPath, formatProperty as EntryPointJsonProperty); + markAsProcessed(fs, packageJson, packageJsonPath, formatProperty as EntryPointJsonProperty); }); } diff --git a/packages/compiler-cli/ngcc/src/packages/build_marker.ts b/packages/compiler-cli/ngcc/src/packages/build_marker.ts index 5aea60f51c..c55dbd39c1 100644 --- a/packages/compiler-cli/ngcc/src/packages/build_marker.ts +++ b/packages/compiler-cli/ngcc/src/packages/build_marker.ts @@ -6,10 +6,8 @@ * found in the LICENSE file at https://angular.io/license */ -import {writeFileSync} from 'fs'; - import {AbsoluteFsPath} from '../../../src/ngtsc/path'; - +import {FileSystem} from '../file_system/file_system'; import {EntryPointJsonProperty, EntryPointPackageJson} from './entry_point'; 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. */ export function markAsProcessed( - packageJson: EntryPointPackageJson, packageJsonPath: AbsoluteFsPath, + fs: FileSystem, packageJson: EntryPointPackageJson, packageJsonPath: AbsoluteFsPath, format: EntryPointJsonProperty) { if (!packageJson.__processed_by_ivy_ngcc__) packageJson.__processed_by_ivy_ngcc__ = {}; packageJson.__processed_by_ivy_ngcc__[format] = NGCC_VERSION; - writeFileSync(packageJsonPath, JSON.stringify(packageJson), 'utf8'); + fs.writeFile(packageJsonPath, JSON.stringify(packageJson)); } diff --git a/packages/compiler-cli/ngcc/src/packages/bundle_program.ts b/packages/compiler-cli/ngcc/src/packages/bundle_program.ts index 5c8ba5eeb3..97ba48e265 100644 --- a/packages/compiler-cli/ngcc/src/packages/bundle_program.ts +++ b/packages/compiler-cli/ngcc/src/packages/bundle_program.ts @@ -5,10 +5,11 @@ * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -import {dirname, resolve} from 'canonical-path'; -import {existsSync, lstatSync, readdirSync} from 'fs'; import * as ts from 'typescript'; +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`, * that are compiled via TypeScript. @@ -21,9 +22,9 @@ export interface BundleProgram { program: ts.Program; options: ts.CompilerOptions; host: ts.CompilerHost; - path: string; + path: AbsoluteFsPath; file: ts.SourceFile; - r3SymbolsPath: string|null; + r3SymbolsPath: AbsoluteFsPath|null; r3SymbolsFile: ts.SourceFile|null; } @@ -31,9 +32,10 @@ export interface BundleProgram { * Create a bundle program. */ export function makeBundleProgram( - isCore: boolean, path: string, r3FileName: string, options: ts.CompilerOptions, - host: ts.CompilerHost): BundleProgram { - const r3SymbolsPath = isCore ? findR3SymbolsPath(dirname(path), r3FileName) : null; + fs: FileSystem, isCore: boolean, path: AbsoluteFsPath, r3FileName: string, + options: ts.CompilerOptions, host: ts.CompilerHost): BundleProgram { + const r3SymbolsPath = + isCore ? findR3SymbolsPath(fs, AbsoluteFsPath.dirname(path), r3FileName) : null; const rootPaths = r3SymbolsPath ? [path, r3SymbolsPath] : [path]; const program = ts.createProgram(rootPaths, options, host); 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. */ -export function findR3SymbolsPath(directory: string, filename: string): string|null { - const r3SymbolsFilePath = resolve(directory, filename); - if (existsSync(r3SymbolsFilePath)) { +export function findR3SymbolsPath( + fs: FileSystem, directory: AbsoluteFsPath, filename: string): AbsoluteFsPath|null { + const r3SymbolsFilePath = AbsoluteFsPath.resolve(directory, filename); + if (fs.exists(r3SymbolsFilePath)) { return r3SymbolsFilePath; } const subDirectories = - readdirSync(directory) + fs.readdir(directory) // Not interested in hidden files .filter(p => !p.startsWith('.')) // Ignore node_modules .filter(p => p !== 'node_modules') // Only interested in directories (and only those that are not symlinks) .filter(p => { - const stat = lstatSync(resolve(directory, p)); + const stat = fs.lstat(AbsoluteFsPath.resolve(directory, p)); return stat.isDirectory() && !stat.isSymbolicLink(); }); for (const subDirectory of subDirectories) { - const r3SymbolsFilePath = findR3SymbolsPath(resolve(directory, subDirectory, ), filename); + const r3SymbolsFilePath = + findR3SymbolsPath(fs, AbsoluteFsPath.resolve(directory, subDirectory), filename); if (r3SymbolsFilePath) { return r3SymbolsFilePath; } diff --git a/packages/compiler-cli/ngcc/src/packages/entry_point.ts b/packages/compiler-cli/ngcc/src/packages/entry_point.ts index 5f1d92fe26..8a8c8bd9bc 100644 --- a/packages/compiler-cli/ngcc/src/packages/entry_point.ts +++ b/packages/compiler-cli/ngcc/src/packages/entry_point.ts @@ -5,14 +5,10 @@ * 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 path from 'canonical-path'; -import * as fs from 'fs'; - import {AbsoluteFsPath} from '../../../src/ngtsc/path'; +import {FileSystem} from '../file_system/file_system'; import {Logger} from '../logging/logger'; - /** * 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. */ export function getEntryPointInfo( - logger: Logger, packagePath: AbsoluteFsPath, entryPointPath: AbsoluteFsPath): EntryPoint|null { - const packageJsonPath = path.resolve(entryPointPath, 'package.json'); - if (!fs.existsSync(packageJsonPath)) { + fs: FileSystem, logger: Logger, packagePath: AbsoluteFsPath, + entryPointPath: AbsoluteFsPath): EntryPoint|null { + const packageJsonPath = AbsoluteFsPath.resolve(entryPointPath, 'package.json'); + if (!fs.exists(packageJsonPath)) { return null; } - const entryPointPackageJson = loadEntryPointPackage(logger, packageJsonPath); + const entryPointPackageJson = loadEntryPointPackage(fs, logger, packageJsonPath); if (!entryPointPackageJson) { return null; } @@ -90,15 +87,15 @@ export function getEntryPointInfo( // Also there must exist a `metadata.json` file next to the typings entry-point. const metadataPath = - path.resolve(entryPointPath, typings.replace(/\.d\.ts$/, '') + '.metadata.json'); + AbsoluteFsPath.resolve(entryPointPath, typings.replace(/\.d\.ts$/, '') + '.metadata.json'); const entryPointInfo: EntryPoint = { name: entryPointPackageJson.name, packageJson: entryPointPackageJson, package: packagePath, path: entryPointPath, - typings: AbsoluteFsPath.from(path.resolve(entryPointPath, typings)), - compiledByAngular: fs.existsSync(metadataPath), + typings: AbsoluteFsPath.resolve(entryPointPath, typings), + compiledByAngular: fs.exists(metadataPath), }; return entryPointInfo; @@ -136,10 +133,10 @@ export function getEntryPointFormat(property: string): EntryPointFormat|undefine * @param packageJsonPath the absolute path to the package.json file. * @returns JSON from the package.json file if it is valid, `null` otherwise. */ -function loadEntryPointPackage(logger: Logger, packageJsonPath: string): EntryPointPackageJson| - null { +function loadEntryPointPackage( + fs: FileSystem, logger: Logger, packageJsonPath: AbsoluteFsPath): EntryPointPackageJson|null { try { - return JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')); + return JSON.parse(fs.readFile(packageJsonPath)); } catch (e) { // We may have run into a package.json with unexpected symbols logger.warn(`Failed to read entry point info from ${packageJsonPath} with error ${e}.`); diff --git a/packages/compiler-cli/ngcc/src/packages/entry_point_bundle.ts b/packages/compiler-cli/ngcc/src/packages/entry_point_bundle.ts index 778577e305..d61f12341b 100644 --- a/packages/compiler-cli/ngcc/src/packages/entry_point_bundle.ts +++ b/packages/compiler-cli/ngcc/src/packages/entry_point_bundle.ts @@ -5,14 +5,14 @@ * 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 {resolve} from 'canonical-path'; import * as ts from 'typescript'; import {AbsoluteFsPath} from '../../../src/ngtsc/path'; +import {FileSystem} from '../file_system/file_system'; import {PathMappings} from '../utils'; - import {BundleProgram, makeBundleProgram} from './bundle_program'; 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 @@ -38,25 +38,27 @@ export interface EntryPointBundle { * @param transformDts Whether to transform the typings along with this bundle. */ export function makeEntryPointBundle( - entryPointPath: string, formatPath: string, typingsPath: string, isCore: boolean, - formatProperty: EntryPointJsonProperty, format: EntryPointFormat, transformDts: boolean, - pathMappings?: PathMappings): EntryPointBundle|null { + fs: FileSystem, entryPointPath: string, formatPath: string, typingsPath: string, + isCore: boolean, formatProperty: EntryPointJsonProperty, format: EntryPointFormat, + transformDts: boolean, pathMappings?: PathMappings): EntryPointBundle|null { // Create the TS program and necessary helpers. const options: ts.CompilerOptions = { allowJs: true, maxNodeModuleJsDepth: Infinity, + noLib: true, rootDir: entryPointPath, ...pathMappings }; - const host = ts.createCompilerHost(options); + const host = new NgccCompilerHost(fs, options); const rootDirs = [AbsoluteFsPath.from(entryPointPath)]; // Create the bundle programs, as necessary. const src = makeBundleProgram( - isCore, resolve(entryPointPath, formatPath), 'r3_symbols.js', options, host); - const dts = transformDts ? - makeBundleProgram( - isCore, resolve(entryPointPath, typingsPath), 'r3_symbols.d.ts', options, host) : - null; + fs, isCore, AbsoluteFsPath.resolve(entryPointPath, formatPath), 'r3_symbols.js', options, + host); + const dts = transformDts ? makeBundleProgram( + fs, isCore, AbsoluteFsPath.resolve(entryPointPath, typingsPath), + 'r3_symbols.d.ts', options, host) : + null; const isFlatCore = isCore && src.r3SymbolsFile === null; return {format, formatProperty, rootDirs, isCore, isFlatCore, src, dts}; diff --git a/packages/compiler-cli/ngcc/src/packages/entry_point_finder.ts b/packages/compiler-cli/ngcc/src/packages/entry_point_finder.ts index c52c0cbba1..f739669480 100644 --- a/packages/compiler-cli/ngcc/src/packages/entry_point_finder.ts +++ b/packages/compiler-cli/ngcc/src/packages/entry_point_finder.ts @@ -5,20 +5,16 @@ * 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 path from 'canonical-path'; -import * as fs from 'fs'; -import {join, resolve} from 'path'; - import {AbsoluteFsPath} from '../../../src/ngtsc/path'; import {DependencyResolver, SortedEntryPointsInfo} from '../dependencies/dependency_resolver'; +import {FileSystem} from '../file_system/file_system'; import {Logger} from '../logging/logger'; import {PathMappings} from '../utils'; - import {EntryPoint, getEntryPointInfo} from './entry_point'; - 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. * @param sourceDirectory An absolute path to the directory to search for entry points. @@ -59,9 +55,9 @@ export class EntryPointFinder { AbsoluteFsPath[] { const basePaths = [sourceDirectory]; if (pathMappings) { - const baseUrl = AbsoluteFsPath.from(resolve(pathMappings.baseUrl)); + const baseUrl = AbsoluteFsPath.resolve(pathMappings.baseUrl); 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. @@ -75,29 +71,29 @@ export class EntryPointFinder { */ private walkDirectoryForEntryPoints(sourceDirectory: AbsoluteFsPath): EntryPoint[] { const entryPoints: EntryPoint[] = []; - fs.readdirSync(sourceDirectory) + this.fs + .readdir(sourceDirectory) // Not interested in hidden files .filter(p => !p.startsWith('.')) // Ignore node_modules .filter(p => p !== 'node_modules') // Only interested in directories (and only those that are not symlinks) .filter(p => { - const stat = fs.lstatSync(path.resolve(sourceDirectory, p)); + const stat = this.fs.lstat(AbsoluteFsPath.resolve(sourceDirectory, p)); return stat.isDirectory() && !stat.isSymbolicLink(); }) .forEach(p => { // Either the directory is a potential package or a namespace containing packages (e.g // `@angular`). - const packagePath = AbsoluteFsPath.from(path.join(sourceDirectory, p)); + const packagePath = AbsoluteFsPath.join(sourceDirectory, p); if (p.startsWith('@')) { entryPoints.push(...this.walkDirectoryForEntryPoints(packagePath)); } else { entryPoints.push(...this.getEntryPointsForPackage(packagePath)); // Also check for any nested node_modules in this package - const nestedNodeModulesPath = - AbsoluteFsPath.from(path.resolve(packagePath, 'node_modules')); - if (fs.existsSync(nestedNodeModulesPath)) { + const nestedNodeModulesPath = AbsoluteFsPath.resolve(packagePath, 'node_modules'); + if (this.fs.exists(nestedNodeModulesPath)) { entryPoints.push(...this.walkDirectoryForEntryPoints(nestedNodeModulesPath)); } } @@ -114,14 +110,14 @@ export class EntryPointFinder { const entryPoints: EntryPoint[] = []; // 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) { entryPoints.push(topLevelEntryPoint); } // Now search all the directories of this package for possible entry points this.walkDirectory(packagePath, subdir => { - const subEntryPoint = getEntryPointInfo(this.logger, packagePath, subdir); + const subEntryPoint = getEntryPointInfo(this.fs, this.logger, packagePath, subdir); if (subEntryPoint !== null) { entryPoints.push(subEntryPoint); } @@ -137,19 +133,19 @@ export class EntryPointFinder { * @param fn the function to apply to each directory. */ private walkDirectory(dir: AbsoluteFsPath, fn: (dir: AbsoluteFsPath) => void) { - return fs - .readdirSync(dir) + return this.fs + .readdir(dir) // Not interested in hidden files .filter(p => !p.startsWith('.')) // Ignore node_modules .filter(p => p !== 'node_modules') // Only interested in directories (and only those that are not symlinks) .filter(p => { - const stat = fs.lstatSync(path.resolve(dir, p)); + const stat = this.fs.lstat(AbsoluteFsPath.resolve(dir, p)); return stat.isDirectory() && !stat.isSymbolicLink(); }) .forEach(subDir => { - const resolvedSubDir = AbsoluteFsPath.from(path.resolve(dir, subDir)); + const resolvedSubDir = AbsoluteFsPath.resolve(dir, subDir); fn(resolvedSubDir); this.walkDirectory(resolvedSubDir, fn); }); diff --git a/packages/compiler-cli/ngcc/src/packages/ngcc_compiler_host.ts b/packages/compiler-cli/ngcc/src/packages/ngcc_compiler_host.ts new file mode 100644 index 0000000000..8f8d5cd597 --- /dev/null +++ b/packages/compiler-cli/ngcc/src/packages/ngcc_compiler_host.ts @@ -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)); + } +} diff --git a/packages/compiler-cli/ngcc/src/packages/transformer.ts b/packages/compiler-cli/ngcc/src/packages/transformer.ts index 3ca19262bf..3abc7078a3 100644 --- a/packages/compiler-cli/ngcc/src/packages/transformer.ts +++ b/packages/compiler-cli/ngcc/src/packages/transformer.ts @@ -12,6 +12,7 @@ import {ModuleWithProvidersAnalyses, ModuleWithProvidersAnalyzer} from '../analy import {NgccReferencesRegistry} from '../analysis/ngcc_references_registry'; import {ExportInfo, PrivateDeclarationsAnalyzer} from '../analysis/private_declarations_analyzer'; import {SwitchMarkerAnalyses, SwitchMarkerAnalyzer} from '../analysis/switch_marker_analyzer'; +import {FileSystem} from '../file_system/file_system'; import {Esm2015ReflectionHost} from '../host/esm2015_host'; import {Esm5ReflectionHost} from '../host/esm5_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. */ export class Transformer { - constructor(private logger: Logger) {} + constructor(private fs: FileSystem, private logger: Logger) {} /** * Transform the source (and typings) files of a bundle. @@ -85,9 +86,9 @@ export class Transformer { getRenderer(host: NgccReflectionHost, isCore: boolean, bundle: EntryPointBundle): Renderer { switch (bundle.format) { case 'esm2015': - return new EsmRenderer(this.logger, host, isCore, bundle); + return new EsmRenderer(this.fs, this.logger, host, isCore, bundle); case 'esm5': - return new Esm5Renderer(this.logger, host, isCore, bundle); + return new Esm5Renderer(this.fs, this.logger, host, isCore, bundle); default: 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 decorationAnalyzer = new DecorationAnalyzer( - bundle.src.program, bundle.src.options, bundle.src.host, typeChecker, reflectionHost, - referencesRegistry, bundle.rootDirs, isCore); + this.fs, bundle.src.program, bundle.src.options, bundle.src.host, typeChecker, + reflectionHost, referencesRegistry, bundle.rootDirs, isCore); const decorationAnalyses = decorationAnalyzer.analyzeProgram(); const moduleWithProvidersAnalyzer = diff --git a/packages/compiler-cli/ngcc/src/rendering/esm5_renderer.ts b/packages/compiler-cli/ngcc/src/rendering/esm5_renderer.ts index 7158b250a1..368c93b44f 100644 --- a/packages/compiler-cli/ngcc/src/rendering/esm5_renderer.ts +++ b/packages/compiler-cli/ngcc/src/rendering/esm5_renderer.ts @@ -5,18 +5,21 @@ * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -import * as ts from 'typescript'; import 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 {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 {EntryPointBundle} from '../packages/entry_point_bundle'; +import {EsmRenderer} from './esm_renderer'; export class Esm5Renderer extends EsmRenderer { - constructor(logger: Logger, host: NgccReflectionHost, isCore: boolean, bundle: EntryPointBundle) { - super(logger, host, isCore, bundle); + constructor( + fs: FileSystem, logger: Logger, host: NgccReflectionHost, isCore: boolean, + bundle: EntryPointBundle) { + super(fs, logger, host, isCore, bundle); } /** diff --git a/packages/compiler-cli/ngcc/src/rendering/esm_renderer.ts b/packages/compiler-cli/ngcc/src/rendering/esm_renderer.ts index 04a3b928f9..15fe6f61fb 100644 --- a/packages/compiler-cli/ngcc/src/rendering/esm_renderer.ts +++ b/packages/compiler-cli/ngcc/src/rendering/esm_renderer.ts @@ -5,20 +5,23 @@ * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -import {dirname, relative} from 'canonical-path'; import MagicString from 'magic-string'; import * as ts from 'typescript'; -import {NgccReflectionHost, POST_R3_MARKER, PRE_R3_MARKER, SwitchableVariableDeclaration} from '../host/ngcc_host'; -import {CompiledClass} from '../analysis/decoration_analyzer'; -import {RedundantDecoratorMap, Renderer, stripExtension} from './renderer'; -import {EntryPointBundle} from '../packages/entry_point_bundle'; -import {ExportInfo} from '../analysis/private_declarations_analyzer'; +import {PathSegment, AbsoluteFsPath} from '../../../src/ngtsc/path'; 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 {EntryPointBundle} from '../packages/entry_point_bundle'; +import {RedundantDecoratorMap, Renderer, stripExtension} from './renderer'; export class EsmRenderer extends Renderer { - constructor(logger: Logger, host: NgccReflectionHost, isCore: boolean, bundle: EntryPointBundle) { - super(logger, host, isCore, bundle); + constructor( + 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); } - addExports(output: MagicString, entryPointBasePath: string, exports: ExportInfo[]): void { + addExports(output: MagicString, entryPointBasePath: AbsoluteFsPath, exports: ExportInfo[]): void { exports.forEach(e => { let exportFrom = ''; const isDtsFile = isDtsPath(entryPointBasePath); @@ -41,7 +44,8 @@ export class EsmRenderer extends Renderer { if (from) { const basePath = stripExtension(from); - const relativePath = './' + relative(dirname(entryPointBasePath), basePath); + const relativePath = + './' + PathSegment.relative(AbsoluteFsPath.dirname(entryPointBasePath), basePath); exportFrom = entryPointBasePath !== basePath ? ` from '${relativePath}'` : ''; } diff --git a/packages/compiler-cli/ngcc/src/rendering/renderer.ts b/packages/compiler-cli/ngcc/src/rendering/renderer.ts index b6016a2ce6..911a8aac75 100644 --- a/packages/compiler-cli/ngcc/src/rendering/renderer.ts +++ b/packages/compiler-cli/ngcc/src/rendering/renderer.ts @@ -6,25 +6,26 @@ * found in the LICENSE file at https://angular.io/license */ 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 {readFileSync, statSync} from 'fs'; +import {SourceMapConverter, commentRegex, fromJSON, fromObject, fromSource, generateMapFileComment, mapFileCommentRegex, removeComments, removeMapFileComments} from 'convert-source-map'; import MagicString from 'magic-string'; -import {basename, dirname, relative, resolve} from 'canonical-path'; import {SourceMapConsumer, SourceMapGenerator, RawSourceMap} from 'source-map'; import * as ts from 'typescript'; -import {NoopImportRewriter, ImportRewriter, R3SymbolsImportRewriter, NOOP_DEFAULT_IMPORT_RECORDER} from '@angular/compiler-cli/src/ngtsc/imports'; -import {CompileResult} from '@angular/compiler-cli/src/ngtsc/transform'; +import {NoopImportRewriter, ImportRewriter, R3SymbolsImportRewriter, NOOP_DEFAULT_IMPORT_RECORDER} from '../../../src/ngtsc/imports'; +import {AbsoluteFsPath, PathSegment} from '../../../src/ngtsc/path'; +import {CompileResult} from '../../../src/ngtsc/transform'; import {translateStatement, translateType, ImportManager} from '../../../src/ngtsc/translator'; -import {NgccFlatImportRewriter} from './ngcc_import_rewriter'; + import {CompiledClass, CompiledFile, DecorationAnalyses} from '../analysis/decoration_analyzer'; import {ModuleWithProvidersInfo, ModuleWithProvidersAnalyses} from '../analysis/module_with_providers_analyzer'; import {PrivateDeclarationsAnalyses, ExportInfo} from '../analysis/private_declarations_analyzer'; import {SwitchMarkerAnalyses, SwitchMarkerAnalysis} from '../analysis/switch_marker_analyzer'; import {IMPORT_PREFIX} from '../constants'; +import {FileSystem} from '../file_system/file_system'; import {NgccReflectionHost, SwitchableVariableDeclaration} from '../host/ngcc_host'; -import {EntryPointBundle} from '../packages/entry_point_bundle'; import {Logger} from '../logging/logger'; +import {EntryPointBundle} from '../packages/entry_point_bundle'; +import {NgccFlatImportRewriter} from './ngcc_import_rewriter'; interface SourceMapInfo { source: string; @@ -39,7 +40,7 @@ export interface FileInfo { /** * Path to where the file should be written. */ - path: string; + path: AbsoluteFsPath; /** * The contents of the file to be be written. */ @@ -81,8 +82,8 @@ export const RedundantDecoratorMap = Map; */ export abstract class Renderer { constructor( - protected logger: Logger, protected host: NgccReflectionHost, protected isCore: boolean, - protected bundle: EntryPointBundle) {} + protected fs: FileSystem, protected logger: Logger, protected host: NgccReflectionHost, + protected isCore: boolean, protected bundle: EntryPointBundle) {} renderProgram( decorationAnalyses: DecorationAnalyses, switchMarkerAnalyses: SwitchMarkerAnalyses, @@ -189,7 +190,7 @@ export abstract class Renderer { this.addModuleWithProvidersParams(outputText, renderInfo.moduleWithProviders, importManager); 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); @@ -207,11 +208,12 @@ export abstract class Renderer { importManager: ImportManager): void { moduleWithProviders.forEach(info => { const ngModuleName = info.ngModule.node.name.text; - const declarationFile = info.declaration.getSourceFile().fileName; - const ngModuleFile = info.ngModule.node.getSourceFile().fileName; + const declarationFile = AbsoluteFsPath.fromSourceFile(info.declaration.getSourceFile()); + const ngModuleFile = AbsoluteFsPath.fromSourceFile(info.ngModule.node.getSourceFile()); const importPath = info.ngModule.viaModule || (declarationFile !== ngModuleFile ? - stripExtension(`./${relative(dirname(declarationFile), ngModuleFile)}`) : + stripExtension( + `./${PathSegment.relative(AbsoluteFsPath.dirname(declarationFile), ngModuleFile)}`) : null); const ngModule = getImportString(importManager, importPath, ngModuleName); @@ -252,7 +254,7 @@ export abstract class Renderer { output: MagicString, imports: {specifier: string, qualifier: string}[], sf: ts.SourceFile): void; protected abstract addExports( - output: MagicString, entryPointBasePath: string, exports: ExportInfo[]): void; + output: MagicString, entryPointBasePath: AbsoluteFsPath, exports: ExportInfo[]): void; protected abstract addDefinitions( output: MagicString, compiledClass: CompiledClass, definitions: string): void; protected abstract removeDecorators( @@ -288,7 +290,7 @@ export abstract class Renderer { */ protected extractSourceMap(file: ts.SourceFile): SourceMapInfo { const inline = commentRegex.test(file.text); - const external = mapFileCommentRegex.test(file.text); + const external = mapFileCommentRegex.exec(file.text); if (inline) { const inlineSourceMap = fromSource(file.text); @@ -300,17 +302,22 @@ export abstract class Renderer { } else if (external) { let externalSourceMap: SourceMapConverter|null = null; 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) { if (e.code === 'ENOENT') { this.logger.warn( `The external map file specified in the source code comment "${e.path}" was not found on the file system.`); - const mapPath = file.fileName + '.map'; - if (basename(e.path) !== basename(mapPath) && statSync(mapPath).isFile()) { + const mapPath = AbsoluteFsPath.fromUnchecked(file.fileName + '.map'); + if (PathSegment.basename(e.path) !== PathSegment.basename(mapPath) && + this.fs.stat(mapPath).isFile()) { 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 { - externalSourceMap = fromObject(JSON.parse(readFileSync(mapPath, 'utf8'))); + externalSourceMap = fromObject(JSON.parse(this.fs.readFile(mapPath))); } catch (e) { this.logger.error(e); } @@ -333,9 +340,9 @@ export abstract class Renderer { */ protected renderSourceAndMap( sourceFile: ts.SourceFile, input: SourceMapInfo, output: MagicString): FileInfo[] { - const outputPath = sourceFile.fileName; - const outputMapPath = `${outputPath}.map`; - const relativeSourcePath = basename(outputPath); + const outputPath = AbsoluteFsPath.fromSourceFile(sourceFile); + const outputMapPath = AbsoluteFsPath.fromUnchecked(`${outputPath}.map`); + const relativeSourcePath = PathSegment.basename(outputPath); const relativeMapPath = `${relativeSourcePath}.map`; const outputMap = output.generateMap({ @@ -502,8 +509,8 @@ export function renderDefinitions( return definitions; } -export function stripExtension(filePath: string): string { - return filePath.replace(/\.(js|d\.ts)$/, ''); +export function stripExtension(filePath: T): T { + return filePath.replace(/\.(js|d\.ts)$/, '') as T; } /** diff --git a/packages/compiler-cli/ngcc/src/writing/in_place_file_writer.ts b/packages/compiler-cli/ngcc/src/writing/in_place_file_writer.ts index 3a5790c85b..6bf43e94ca 100644 --- a/packages/compiler-cli/ngcc/src/writing/in_place_file_writer.ts +++ b/packages/compiler-cli/ngcc/src/writing/in_place_file_writer.ts @@ -6,15 +6,11 @@ * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ - -import {dirname} from 'canonical-path'; -import {existsSync, writeFileSync} from 'fs'; -import {mkdir, mv} from 'shelljs'; - +import {AbsoluteFsPath} from '../../../src/ngtsc/path'; +import {FileSystem} from '../file_system/file_system'; import {EntryPoint} from '../packages/entry_point'; import {EntryPointBundle} from '../packages/entry_point_bundle'; import {FileInfo} from '../rendering/renderer'; - 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. */ export class InPlaceFileWriter implements FileWriter { + constructor(protected fs: FileSystem) {} + writeBundle(_entryPoint: EntryPoint, _bundle: EntryPointBundle, transformedFiles: FileInfo[]) { transformedFiles.forEach(file => this.writeFileAndBackup(file)); } + protected writeFileAndBackup(file: FileInfo): void { - mkdir('-p', dirname(file.path)); - const backPath = file.path + '.__ivy_ngcc_bak'; - if (existsSync(backPath)) { + this.fs.ensureDir(AbsoluteFsPath.dirname(file.path)); + const backPath = AbsoluteFsPath.fromUnchecked(`${file.path}.__ivy_ngcc_bak`); + if (this.fs.exists(backPath)) { throw new Error( `Tried to overwrite ${backPath} with an ngcc back up file, which is disallowed.`); } - if (existsSync(file.path)) { - mv(file.path, backPath); + if (this.fs.exists(file.path)) { + this.fs.moveFile(file.path, backPath); } - writeFileSync(file.path, file.contents, 'utf8'); + this.fs.writeFile(file.path, file.contents); } } diff --git a/packages/compiler-cli/ngcc/src/writing/new_entry_point_file_writer.ts b/packages/compiler-cli/ngcc/src/writing/new_entry_point_file_writer.ts index f95160045f..10414ebdfc 100644 --- a/packages/compiler-cli/ngcc/src/writing/new_entry_point_file_writer.ts +++ b/packages/compiler-cli/ngcc/src/writing/new_entry_point_file_writer.ts @@ -6,12 +6,7 @@ * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ - -import {dirname, join, relative} from 'canonical-path'; -import {writeFileSync} from 'fs'; -import {cp, mkdir} from 'shelljs'; - -import {AbsoluteFsPath} from '../../../src/ngtsc/path'; +import {AbsoluteFsPath, PathSegment} from '../../../src/ngtsc/path'; import {isDtsPath} from '../../../src/ngtsc/util/src/typescript'; import {EntryPoint, EntryPointJsonProperty} from '../packages/entry_point'; import {EntryPointBundle} from '../packages/entry_point_bundle'; @@ -32,7 +27,7 @@ const NGCC_DIRECTORY = '__ivy_ngcc__'; export class NewEntryPointFileWriter extends InPlaceFileWriter { writeBundle(entryPoint: EntryPoint, bundle: EntryPointBundle, transformedFiles: FileInfo[]) { // 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); transformedFiles.forEach(file => this.writeFile(file, entryPoint.package, ngccFolder)); this.updatePackageJson(entryPoint, bundle.formatProperty, ngccFolder); @@ -41,12 +36,13 @@ export class NewEntryPointFileWriter extends InPlaceFileWriter { protected copyBundle( bundle: EntryPointBundle, packagePath: AbsoluteFsPath, ngccFolder: AbsoluteFsPath) { bundle.src.program.getSourceFiles().forEach(sourceFile => { - const relativePath = relative(packagePath, sourceFile.fileName); + const relativePath = + PathSegment.relative(packagePath, AbsoluteFsPath.fromSourceFile(sourceFile)); const isOutsidePackage = relativePath.startsWith('..'); if (!sourceFile.isDeclarationFile && !isOutsidePackage) { - const newFilePath = join(ngccFolder, relativePath); - mkdir('-p', dirname(newFilePath)); - cp(sourceFile.fileName, newFilePath); + const newFilePath = AbsoluteFsPath.join(ngccFolder, relativePath); + this.fs.ensureDir(AbsoluteFsPath.dirname(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 super.writeFileAndBackup(file); } else { - const relativePath = relative(packagePath, file.path); - const newFilePath = join(ngccFolder, relativePath); - mkdir('-p', dirname(newFilePath)); - writeFileSync(newFilePath, file.contents, 'utf8'); + const relativePath = PathSegment.relative(packagePath, file.path); + const newFilePath = AbsoluteFsPath.join(ngccFolder, relativePath); + this.fs.ensureDir(AbsoluteFsPath.dirname(newFilePath)); + this.fs.writeFile(newFilePath, file.contents); } } protected updatePackageJson( entryPoint: EntryPoint, formatProperty: EntryPointJsonProperty, ngccFolder: AbsoluteFsPath) { - const formatPath = join(entryPoint.path, entryPoint.packageJson[formatProperty] !); - const newFormatPath = join(ngccFolder, relative(entryPoint.package, formatPath)); + const formatPath = + AbsoluteFsPath.join(entryPoint.path, entryPoint.packageJson[formatProperty] !); + const newFormatPath = + AbsoluteFsPath.join(ngccFolder, PathSegment.relative(entryPoint.package, formatPath)); const newFormatProperty = formatProperty + '_ivy_ngcc'; - (entryPoint.packageJson as any)[newFormatProperty] = relative(entryPoint.path, newFormatPath); - writeFileSync(join(entryPoint.path, 'package.json'), JSON.stringify(entryPoint.packageJson)); + (entryPoint.packageJson as any)[newFormatProperty] = + PathSegment.relative(entryPoint.path, newFormatPath); + this.fs.writeFile( + AbsoluteFsPath.join(entryPoint.path, 'package.json'), + JSON.stringify(entryPoint.packageJson)); } } diff --git a/packages/compiler-cli/ngcc/test/analysis/decoration_analyzer_spec.ts b/packages/compiler-cli/ngcc/test/analysis/decoration_analyzer_spec.ts index c3f629bacd..afca349809 100644 --- a/packages/compiler-cli/ngcc/test/analysis/decoration_analyzer_spec.ts +++ b/packages/compiler-cli/ngcc/test/analysis/decoration_analyzer_spec.ts @@ -12,6 +12,7 @@ import {Decorator} from '../../../src/ngtsc/reflection'; import {DecoratorHandler, DetectResult} from '../../../src/ngtsc/transform'; import {CompiledClass, DecorationAnalyses, DecorationAnalyzer} from '../../src/analysis/decoration_analyzer'; 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 {MockLogger} from '../helpers/mock_logger'; import {makeTestBundleProgram} from '../helpers/utils'; @@ -136,8 +137,9 @@ describe('DecorationAnalyzer', () => { const reflectionHost = new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker()); const referencesRegistry = new NgccReferencesRegistry(reflectionHost); + const fs = new NodeJSFileSystem(); const analyzer = new DecorationAnalyzer( - program, options, host, program.getTypeChecker(), reflectionHost, referencesRegistry, + fs, program, options, host, program.getTypeChecker(), reflectionHost, referencesRegistry, [AbsoluteFsPath.fromUnchecked('/')], false); testHandler = createTestHandler(); analyzer.handlers = [testHandler]; diff --git a/packages/compiler-cli/ngcc/test/analysis/private_declarations_analyzer_spec.ts b/packages/compiler-cli/ngcc/test/analysis/private_declarations_analyzer_spec.ts index 85a7edda8e..e4bd4acb3b 100644 --- a/packages/compiler-cli/ngcc/test/analysis/private_declarations_analyzer_spec.ts +++ b/packages/compiler-cli/ngcc/test/analysis/private_declarations_analyzer_spec.ts @@ -9,12 +9,15 @@ import * as ts from 'typescript'; import {Reference} from '../../../src/ngtsc/imports'; +import {AbsoluteFsPath} from '../../../src/ngtsc/path'; import {NgccReferencesRegistry} from '../../src/analysis/ngcc_references_registry'; import {PrivateDeclarationsAnalyzer} from '../../src/analysis/private_declarations_analyzer'; import {Esm2015ReflectionHost} from '../../src/host/esm2015_host'; import {MockLogger} from '../helpers/mock_logger'; import {getDeclaration, makeTestBundleProgram, makeTestProgram} from '../helpers/utils'; +const _ = AbsoluteFsPath.fromUnchecked; + describe('PrivateDeclarationsAnalyzer', () => { describe('analyzeProgram()', () => { @@ -147,11 +150,11 @@ describe('PrivateDeclarationsAnalyzer', () => { // not added to the ReferencesRegistry (i.e. they were not declared in an NgModule). expect(analyses.length).toEqual(2); 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', - from: '/src/c.js', - dtsFrom: '/typings/c.d.ts', + from: _('/src/c.js'), + dtsFrom: _('/typings/c.d.ts'), alias: null }, ]); @@ -207,7 +210,7 @@ describe('PrivateDeclarationsAnalyzer', () => { const analyses = analyzer.analyzeProgram(program); expect(analyses).toEqual([{ identifier: 'ComponentOne', - from: '/src/a.js', + from: _('/src/a.js'), dtsFrom: null, alias: 'aliasedComponentOne', }]); diff --git a/packages/compiler-cli/ngcc/test/analysis/switch_marker_analyzer_spec.ts b/packages/compiler-cli/ngcc/test/analysis/switch_marker_analyzer_spec.ts index 5edbfb827b..f97ed131e2 100644 --- a/packages/compiler-cli/ngcc/test/analysis/switch_marker_analyzer_spec.ts +++ b/packages/compiler-cli/ngcc/test/analysis/switch_marker_analyzer_spec.ts @@ -5,8 +5,6 @@ * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -import * as ts from 'typescript'; - import {SwitchMarkerAnalyzer} from '../../src/analysis/switch_marker_analyzer'; import {Esm2015ReflectionHost} from '../../src/host/esm2015_host'; import {MockLogger} from '../helpers/mock_logger'; diff --git a/packages/compiler-cli/ngcc/test/dependencies/dependency_resolver_spec.ts b/packages/compiler-cli/ngcc/test/dependencies/dependency_resolver_spec.ts index 452aef03f5..b97ee13820 100644 --- a/packages/compiler-cli/ngcc/test/dependencies/dependency_resolver_spec.ts +++ b/packages/compiler-cli/ngcc/test/dependencies/dependency_resolver_spec.ts @@ -9,6 +9,7 @@ import {AbsoluteFsPath} from '../../../src/ngtsc/path'; import {DependencyResolver, SortedEntryPointsInfo} from '../../src/dependencies/dependency_resolver'; import {EsmDependencyHost} from '../../src/dependencies/esm_dependency_host'; 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 {MockLogger} from '../helpers/mock_logger'; @@ -18,7 +19,8 @@ describe('DependencyResolver', () => { let host: EsmDependencyHost; let resolver: DependencyResolver; beforeEach(() => { - host = new EsmDependencyHost(new ModuleResolver()); + const fs = new NodeJSFileSystem(); + host = new EsmDependencyHost(fs, new ModuleResolver(fs)); resolver = new DependencyResolver(new MockLogger(), host); }); describe('sortEntryPointsByDependency()', () => { diff --git a/packages/compiler-cli/ngcc/test/dependencies/esm_dependency_host_spec.ts b/packages/compiler-cli/ngcc/test/dependencies/esm_dependency_host_spec.ts index 7c1612c739..bb3dc79c9b 100644 --- a/packages/compiler-cli/ngcc/test/dependencies/esm_dependency_host_spec.ts +++ b/packages/compiler-cli/ngcc/test/dependencies/esm_dependency_host_spec.ts @@ -11,12 +11,16 @@ import * as ts from 'typescript'; import {AbsoluteFsPath} from '../../../src/ngtsc/path'; import {EsmDependencyHost} from '../../src/dependencies/esm_dependency_host'; import {ModuleResolver} from '../../src/dependencies/module_resolver'; +import {NodeJSFileSystem} from '../../src/file_system/node_js_file_system'; const _ = AbsoluteFsPath.from; describe('DependencyHost', () => { let host: EsmDependencyHost; - beforeEach(() => host = new EsmDependencyHost(new ModuleResolver())); + beforeEach(() => { + const fs = new NodeJSFileSystem(); + host = new EsmDependencyHost(fs, new ModuleResolver(fs)); + }); describe('getDependencies()', () => { beforeEach(createMockFileSystem); @@ -94,13 +98,14 @@ describe('DependencyHost', () => { }); it('should support `paths` alias mappings when resolving modules', () => { - host = new EsmDependencyHost(new ModuleResolver({ - baseUrl: '/dist', - paths: { - '@app/*': ['*'], - '@lib/*/test': ['lib/*/test'], - } - })); + const fs = new NodeJSFileSystem(); + host = new EsmDependencyHost(fs, new ModuleResolver(fs, { + baseUrl: '/dist', + paths: { + '@app/*': ['*'], + '@lib/*/test': ['lib/*/test'], + } + })); const {dependencies, missing, deepImports} = host.findDependencies(_('/path-alias/index.js')); expect(dependencies.size).toBe(4); expect(dependencies.has(_('/dist/components'))).toBe(true); diff --git a/packages/compiler-cli/ngcc/test/dependencies/module_resolver_spec.ts b/packages/compiler-cli/ngcc/test/dependencies/module_resolver_spec.ts index 550c66fcd3..b6c96c824d 100644 --- a/packages/compiler-cli/ngcc/test/dependencies/module_resolver_spec.ts +++ b/packages/compiler-cli/ngcc/test/dependencies/module_resolver_spec.ts @@ -10,6 +10,7 @@ import * as mockFs from 'mock-fs'; import {AbsoluteFsPath} from '../../../src/ngtsc/path'; import {ModuleResolver, ResolvedDeepImport, ResolvedExternalModule, ResolvedRelativeModule} from '../../src/dependencies/module_resolver'; +import {NodeJSFileSystem} from '../../src/file_system/node_js_file_system'; const _ = AbsoluteFsPath.from; @@ -78,7 +79,8 @@ describe('ModuleResolver', () => { describe('resolveModule()', () => { describe('with relative paths', () => { 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'))) .toEqual(new ResolvedRelativeModule(_('/libs/local-package/x.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', () => { - const resolver = new ModuleResolver(); + const fs = new NodeJSFileSystem(); + const resolver = new ModuleResolver(fs); expect(resolver.resolveModuleImport('./y', _('/libs/local-package/index.js'))).toBe(null); }); }); describe('with non-mapped external paths', () => { 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'))) .toEqual(new ResolvedExternalModule(_('/libs/local-package/node_modules/package-1'))); expect( @@ -106,7 +110,8 @@ describe('ModuleResolver', () => { }); 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'))) .toEqual(new ResolvedExternalModule(_('/libs/node_modules/package-2'))); 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', () => { - const resolver = new ModuleResolver(); + const fs = new NodeJSFileSystem(); + const resolver = new ModuleResolver(fs); expect(resolver.resolveModuleImport('missing-2', _('/libs/local-package/index.js'))) .toBe(null); }); 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'))) .toBe(null); }); it('should identify deep imports into an external module', () => { - const resolver = new ModuleResolver(); + const fs = new NodeJSFileSystem(); + const resolver = new ModuleResolver(fs); expect( resolver.resolveModuleImport('package-1/sub-folder', _('/libs/local-package/index.js'))) .toEqual( @@ -137,8 +145,9 @@ describe('ModuleResolver', () => { describe('with mapped path external modules', () => { it('should resolve to the package.json of simple mapped packages', () => { + const fs = new NodeJSFileSystem(); 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'))) .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 *', () => { - const resolver = new ModuleResolver({ + const fs = new NodeJSFileSystem(); + const resolver = new ModuleResolver(fs, { baseUrl: '/dist', paths: { '@lib/*': ['*'], @@ -166,25 +176,28 @@ describe('ModuleResolver', () => { it('should follow the ordering of `paths` when matching mapped packages', () => { 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'))) .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'))) .toEqual(new ResolvedExternalModule(_('/dist/sub-folder/package-4'))); }); it('should resolve packages when the path mappings have post-fixes', () => { + const fs = new NodeJSFileSystem(); 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'))) .toEqual(new ResolvedExternalModule(_('/dist/sub-folder/package-5/post-fix'))); }); it('should match paths against complex path matchers', () => { + const fs = new NodeJSFileSystem(); 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'))) .toEqual(new ResolvedExternalModule(_('/dist/sub-folder/package-4'))); 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', () => { - const resolver = new ModuleResolver({baseUrl: '/dist', paths: {'@shared/*': ['*']}}); + const fs = new NodeJSFileSystem(); + const resolver = new ModuleResolver(fs, {baseUrl: '/dist', paths: {'@shared/*': ['*']}}); expect(resolver.resolveModuleImport( '@shared/package-4/x', _('/dist/package-4/sub-folder/index.js'))) .toEqual(new ResolvedRelativeModule(_('/dist/package-4/x.js'))); }); it('should resolve paths where the wildcard matches more than one path segment', () => { - const resolver = - new ModuleResolver({baseUrl: '/dist', paths: {'@shared/*/post-fix': ['*/post-fix']}}); + const fs = new NodeJSFileSystem(); + const resolver = new ModuleResolver( + fs, {baseUrl: '/dist', paths: {'@shared/*/post-fix': ['*/post-fix']}}); expect( resolver.resolveModuleImport( '@shared/sub-folder/package-5/post-fix', _('/dist/package-4/sub-folder/index.js'))) diff --git a/packages/compiler-cli/ngcc/test/helpers/utils.ts b/packages/compiler-cli/ngcc/test/helpers/utils.ts index 6969c51ea5..0cd665c246 100644 --- a/packages/compiler-cli/ngcc/test/helpers/utils.ts +++ b/packages/compiler-cli/ngcc/test/helpers/utils.ts @@ -15,6 +15,7 @@ import {EntryPointBundle} from '../../src/packages/entry_point_bundle'; export {getDeclaration} from '../../../src/ngtsc/testing/in_memory_typescript'; +const _ = AbsoluteFsPath.fromUnchecked; /** * * @param format The format of the bundle. @@ -28,11 +29,7 @@ export function makeTestEntryPointBundle( const src = makeTestBundleProgram(files); const dts = dtsFiles ? makeTestBundleProgram(dtsFiles) : null; const isFlatCore = isCore && src.r3SymbolsFile === null; - return { - formatProperty, - format, - rootDirs: [AbsoluteFsPath.fromUnchecked('/')], src, dts, isCore, isFlatCore - }; + return {formatProperty, format, rootDirs: [_('/')], src, dts, isCore, isFlatCore}; } /** @@ -41,10 +38,10 @@ export function makeTestEntryPointBundle( */ export function makeTestBundleProgram(files: {name: string, contents: string}[]): BundleProgram { const {program, options, host} = makeTestProgramInternal(...files); - const path = files[0].name; + const path = _(files[0].name); const file = program.getSourceFile(path) !; const r3SymbolsInfo = files.find(file => file.name.indexOf('r3_symbols') !== -1) || null; - const r3SymbolsPath = r3SymbolsInfo && r3SymbolsInfo.name; + const r3SymbolsPath = r3SymbolsInfo && _(r3SymbolsInfo.name); const r3SymbolsFile = r3SymbolsPath && program.getSourceFile(r3SymbolsPath) || null; return {program, options, host, path, file, r3SymbolsPath, r3SymbolsFile}; } diff --git a/packages/compiler-cli/ngcc/test/host/esm2015_host_spec.ts b/packages/compiler-cli/ngcc/test/host/esm2015_host_spec.ts index 1e61fc583c..98e20267ff 100644 --- a/packages/compiler-cli/ngcc/test/host/esm2015_host_spec.ts +++ b/packages/compiler-cli/ngcc/test/host/esm2015_host_spec.ts @@ -8,7 +8,7 @@ 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 {MockLogger} from '../helpers/mock_logger'; import {getDeclaration, makeTestBundleProgram, makeTestProgram} from '../helpers/utils'; @@ -1007,7 +1007,7 @@ describe('Esm2015ReflectionHost', () => { const parameters = host.getConstructorParameters(classNode) !; expect(parameters.length).toBe(1); - expect(parameters[0]).toEqual(jasmine.objectContaining({ + expect(parameters[0]).toEqual(jasmine.objectContaining({ name: 'arg1', decorators: [], })); @@ -1021,7 +1021,7 @@ describe('Esm2015ReflectionHost', () => { const parameters = host.getConstructorParameters(classNode) !; expect(parameters.length).toBe(1); - expect(parameters[0]).toEqual(jasmine.objectContaining({ + expect(parameters[0]).toEqual(jasmine.objectContaining({ name: 'arg1', decorators: null, })); @@ -1035,7 +1035,7 @@ describe('Esm2015ReflectionHost', () => { const parameters = host.getConstructorParameters(classNode) !; expect(parameters.length).toBe(1); - expect(parameters[0]).toEqual(jasmine.objectContaining({ + expect(parameters[0]).toEqual(jasmine.objectContaining({ name: 'arg1', decorators: null, })); @@ -1115,11 +1115,11 @@ describe('Esm2015ReflectionHost', () => { const parameters = host.getConstructorParameters(classNode); expect(parameters !.length).toBe(2); - expect(parameters ![0]).toEqual(jasmine.objectContaining({ + expect(parameters ![0]).toEqual(jasmine.objectContaining({ name: 'arg1', decorators: null, })); - expect(parameters ![1]).toEqual(jasmine.objectContaining({ + expect(parameters ![1]).toEqual(jasmine.objectContaining({ name: 'arg2', decorators: jasmine.any(Array) as any })); diff --git a/packages/compiler-cli/ngcc/test/integration/ngcc_spec.ts b/packages/compiler-cli/ngcc/test/integration/ngcc_spec.ts index 1ad2c9f6aa..f84dd23684 100644 --- a/packages/compiler-cli/ngcc/test/integration/ngcc_spec.ts +++ b/packages/compiler-cli/ngcc/test/integration/ngcc_spec.ts @@ -12,6 +12,7 @@ import * as mockFs from 'mock-fs'; import {join} from 'path'; import {getAngularPackagesFromRunfiles, resolveNpmTreeArtifact} from '../../../test/runfile_helpers'; +import {NodeJSFileSystem} from '../../src/file_system/node_js_file_system'; import {mainNgcc} from '../../src/main'; import {markAsProcessed} from '../../src/packages/build_marker'; import {EntryPointJsonProperty, EntryPointPackageJson, SUPPORTED_FORMAT_PROPERTIES} from '../../src/packages/entry_point'; @@ -144,8 +145,10 @@ describe('ngcc main()', () => { const basePath = '/node_modules'; const targetPackageJsonPath = _(join(basePath, packagePath, 'package.json')); const targetPackage = loadPackage(packagePath); - markAsProcessed(targetPackage, targetPackageJsonPath, 'typings'); - properties.forEach(property => markAsProcessed(targetPackage, targetPackageJsonPath, property)); + const fs = new NodeJSFileSystem(); + markAsProcessed(fs, targetPackage, targetPackageJsonPath, 'typings'); + properties.forEach( + property => markAsProcessed(fs, targetPackage, targetPackageJsonPath, property)); } diff --git a/packages/compiler-cli/ngcc/test/packages/build_marker_spec.ts b/packages/compiler-cli/ngcc/test/packages/build_marker_spec.ts index d57061cc2d..da3b3d124f 100644 --- a/packages/compiler-cli/ngcc/test/packages/build_marker_spec.ts +++ b/packages/compiler-cli/ngcc/test/packages/build_marker_spec.ts @@ -6,12 +6,12 @@ * 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 {AbsoluteFsPath} from '../../../src/ngtsc/path'; +import {NodeJSFileSystem} from '../../src/file_system/node_js_file_system'; import {hasBeenProcessed, markAsProcessed} from '../../src/packages/build_marker'; -import {EntryPoint} from '../../src/packages/entry_point'; function createMockFileSystem() { mockFs({ @@ -106,21 +106,24 @@ describe('Marker files', () => { 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')); expect(pkg.__processed_by_ivy_ngcc__.fesm2015).toEqual('0.0.0-PLACEHOLDER'); 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')); expect(pkg.__processed_by_ivy_ngcc__.fesm2015).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', () => { + const fs = new NodeJSFileSystem(); let pkg = JSON.parse(readFileSync(COMMON_PACKAGE_PATH, 'utf8')); 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'); }); }); diff --git a/packages/compiler-cli/ngcc/test/packages/entry_point_finder_spec.ts b/packages/compiler-cli/ngcc/test/packages/entry_point_finder_spec.ts index e16eef9275..c2630db9af 100644 --- a/packages/compiler-cli/ngcc/test/packages/entry_point_finder_spec.ts +++ b/packages/compiler-cli/ngcc/test/packages/entry_point_finder_spec.ts @@ -12,6 +12,7 @@ import {AbsoluteFsPath} from '../../../src/ngtsc/path'; import {DependencyResolver} from '../../src/dependencies/dependency_resolver'; import {EsmDependencyHost} from '../../src/dependencies/esm_dependency_host'; 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 {EntryPointFinder} from '../../src/packages/entry_point_finder'; import {MockLogger} from '../helpers/mock_logger'; @@ -22,12 +23,13 @@ describe('findEntryPoints()', () => { let resolver: DependencyResolver; let finder: EntryPointFinder; beforeEach(() => { + const fs = new NodeJSFileSystem(); 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[]) => { return {entryPoints, ignoredEntryPoints: [], ignoredDependencies: []}; }); - finder = new EntryPointFinder(new MockLogger(), resolver); + finder = new EntryPointFinder(fs, new MockLogger(), resolver); }); beforeEach(createMockFileSystem); afterEach(restoreRealFileSystem); diff --git a/packages/compiler-cli/ngcc/test/packages/entry_point_spec.ts b/packages/compiler-cli/ngcc/test/packages/entry_point_spec.ts index a4c028d328..7505027557 100644 --- a/packages/compiler-cli/ngcc/test/packages/entry_point_spec.ts +++ b/packages/compiler-cli/ngcc/test/packages/entry_point_spec.ts @@ -6,24 +6,27 @@ * found in the LICENSE file at https://angular.io/license */ -import {AbsoluteFsPath} from '@angular/compiler-cli/src/ngtsc/path'; import {readFileSync} from '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 {MockLogger} from '../helpers/mock_logger'; +const _ = AbsoluteFsPath.fromUnchecked; + describe('getEntryPointInfo()', () => { beforeEach(createMockFileSystem); afterEach(restoreRealFileSystem); - const _ = AbsoluteFsPath.from; const SOME_PACKAGE = _('/some_package'); it('should return an object containing absolute paths to the formats of the specified entry-point', () => { - const entryPoint = - getEntryPointInfo(new MockLogger(), SOME_PACKAGE, _('/some_package/valid_entry_point')); + const fs = new NodeJSFileSystem(); + const entryPoint = getEntryPointInfo( + fs, new MockLogger(), SOME_PACKAGE, _('/some_package/valid_entry_point')); expect(entryPoint).toEqual({ name: 'some-package/valid_entry_point', package: SOME_PACKAGE, @@ -35,21 +38,24 @@ describe('getEntryPointInfo()', () => { }); it('should return null if there is no package.json at the entry-point path', () => { - const entryPoint = - getEntryPointInfo(new MockLogger(), SOME_PACKAGE, _('/some_package/missing_package_json')); + const fs = new NodeJSFileSystem(); + const entryPoint = getEntryPointInfo( + fs, new MockLogger(), SOME_PACKAGE, _('/some_package/missing_package_json')); expect(entryPoint).toBe(null); }); it('should return null if there is no typings or types field in the package.json', () => { + const fs = new NodeJSFileSystem(); 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); }); it('should return an object with `compiledByAngular` set to false if there is no metadata.json file next to the typing file', () => { - const entryPoint = - getEntryPointInfo(new MockLogger(), SOME_PACKAGE, _('/some_package/missing_metadata')); + const fs = new NodeJSFileSystem(); + const entryPoint = getEntryPointInfo( + fs, new MockLogger(), SOME_PACKAGE, _('/some_package/missing_metadata')); expect(entryPoint).toEqual({ name: 'some-package/missing_metadata', package: SOME_PACKAGE, @@ -61,8 +67,9 @@ describe('getEntryPointInfo()', () => { }); it('should work if the typings field is named `types', () => { + const fs = new NodeJSFileSystem(); 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({ name: 'some-package/types_rather_than_typings', package: SOME_PACKAGE, @@ -74,8 +81,9 @@ describe('getEntryPointInfo()', () => { }); it('should work with Angular Material style package.json', () => { + const fs = new NodeJSFileSystem(); const entryPoint = - getEntryPointInfo(new MockLogger(), SOME_PACKAGE, _('/some_package/material_style')); + getEntryPointInfo(fs, new MockLogger(), SOME_PACKAGE, _('/some_package/material_style')); expect(entryPoint).toEqual({ name: 'some_package/material_style', package: SOME_PACKAGE, @@ -87,8 +95,9 @@ describe('getEntryPointInfo()', () => { }); it('should return null if the package.json is not valid JSON', () => { - const entryPoint = - getEntryPointInfo(new MockLogger(), SOME_PACKAGE, _('/some_package/unexpected_symbols')); + const fs = new NodeJSFileSystem(); + const entryPoint = getEntryPointInfo( + fs, new MockLogger(), SOME_PACKAGE, _('/some_package/unexpected_symbols')); expect(entryPoint).toBe(null); }); }); diff --git a/packages/compiler-cli/ngcc/test/rendering/esm2015_renderer_spec.ts b/packages/compiler-cli/ngcc/test/rendering/esm2015_renderer_spec.ts index c28d25edb0..9b6b808798 100644 --- a/packages/compiler-cli/ngcc/test/rendering/esm2015_renderer_spec.ts +++ b/packages/compiler-cli/ngcc/test/rendering/esm2015_renderer_spec.ts @@ -5,31 +5,33 @@ * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -import {dirname} from 'canonical-path'; import MagicString from 'magic-string'; import * as ts from 'typescript'; import {AbsoluteFsPath} from '../../../src/ngtsc/path'; import {DecorationAnalyzer} from '../../src/analysis/decoration_analyzer'; import {NgccReferencesRegistry} from '../../src/analysis/ngcc_references_registry'; 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 {EsmRenderer} from '../../src/rendering/esm_renderer'; import {makeTestEntryPointBundle} from '../helpers/utils'; import {MockLogger} from '../helpers/mock_logger'; +const _ = AbsoluteFsPath.fromUnchecked; + function setup(file: {name: string, contents: string}) { const logger = new MockLogger(); const bundle = makeTestEntryPointBundle('es2015', 'esm2015', false, [file]) !; const typeChecker = bundle.src.program.getTypeChecker(); const host = new Esm2015ReflectionHost(logger, false, typeChecker); const referencesRegistry = new NgccReferencesRegistry(host); - const decorationAnalyses = - new DecorationAnalyzer( - bundle.src.program, bundle.src.options, bundle.src.host, typeChecker, host, - referencesRegistry, [AbsoluteFsPath.fromUnchecked('/')], false) - .analyzeProgram(); + const fs = new NodeJSFileSystem(); + const decorationAnalyses = new DecorationAnalyzer( + fs, bundle.src.program, bundle.src.options, bundle.src.host, + typeChecker, host, referencesRegistry, [_('/')], false) + .analyzeProgram(); 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 { host, 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', () => { const {renderer} = setup(PROGRAM); const output = new MagicString(PROGRAM.contents); - 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: 'ComponentA2'}, - {from: '/some/foo/b.js', dtsFrom: '/some/foo/b.d.ts', identifier: 'ComponentB'}, - {from: PROGRAM.name, dtsFrom: PROGRAM.name, identifier: 'TopLevelComponent'}, + 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: 'ComponentA2'}, + {from: _('/some/foo/b.js'), dtsFrom: _('/some/foo/b.d.ts'), identifier: 'ComponentB'}, + {from: _(PROGRAM.name), dtsFrom: _(PROGRAM.name), identifier: 'TopLevelComponent'}, ]); expect(output.toString()).toContain(` // Some other content @@ -153,11 +155,11 @@ export {TopLevelComponent};`); it('should not insert alias exports in js output', () => { const {renderer} = setup(PROGRAM); const output = new MagicString(PROGRAM.contents); - renderer.addExports(output, PROGRAM.name.replace(/\.js$/, ''), [ - {from: '/some/a.js', alias: 'eComponentA1', identifier: 'ComponentA1'}, - {from: '/some/a.js', alias: 'eComponentA2', identifier: 'ComponentA2'}, - {from: '/some/foo/b.js', alias: 'eComponentB', identifier: 'ComponentB'}, - {from: PROGRAM.name, alias: 'eTopLevelComponent', identifier: 'TopLevelComponent'}, + renderer.addExports(output, _(PROGRAM.name.replace(/\.js$/, '')), [ + {from: _('/some/a.js'), alias: 'eComponentA1', identifier: 'ComponentA1'}, + {from: _('/some/a.js'), alias: 'eComponentA2', identifier: 'ComponentA2'}, + {from: _('/some/foo/b.js'), alias: 'eComponentB', identifier: 'ComponentB'}, + {from: _(PROGRAM.name), alias: 'eTopLevelComponent', identifier: 'TopLevelComponent'}, ]); const outputString = output.toString(); expect(outputString).not.toContain(`{eComponentA1 as ComponentA1}`); diff --git a/packages/compiler-cli/ngcc/test/rendering/esm5_renderer_spec.ts b/packages/compiler-cli/ngcc/test/rendering/esm5_renderer_spec.ts index 8e6e058633..8cf050426a 100644 --- a/packages/compiler-cli/ngcc/test/rendering/esm5_renderer_spec.ts +++ b/packages/compiler-cli/ngcc/test/rendering/esm5_renderer_spec.ts @@ -5,31 +5,33 @@ * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -import {dirname} from 'canonical-path'; import MagicString from 'magic-string'; import * as ts from 'typescript'; import {AbsoluteFsPath} from '../../../src/ngtsc/path'; import {DecorationAnalyzer} from '../../src/analysis/decoration_analyzer'; import {NgccReferencesRegistry} from '../../src/analysis/ngcc_references_registry'; 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 {Esm5Renderer} from '../../src/rendering/esm5_renderer'; import {makeTestEntryPointBundle, getDeclaration} from '../helpers/utils'; import {MockLogger} from '../helpers/mock_logger'; +const _ = AbsoluteFsPath.fromUnchecked; + function setup(file: {name: string, contents: string}) { const logger = new MockLogger(); const bundle = makeTestEntryPointBundle('module', 'esm5', false, [file]); const typeChecker = bundle.src.program.getTypeChecker(); const host = new Esm5ReflectionHost(logger, false, typeChecker); const referencesRegistry = new NgccReferencesRegistry(host); - const decorationAnalyses = - new DecorationAnalyzer( - bundle.src.program, bundle.src.options, bundle.src.host, typeChecker, host, - referencesRegistry, [AbsoluteFsPath.fromUnchecked('/')], false) - .analyzeProgram(); + const fs = new NodeJSFileSystem(); + const decorationAnalyses = new DecorationAnalyzer( + fs, bundle.src.program, bundle.src.options, bundle.src.host, + typeChecker, host, referencesRegistry, [_('/')], false) + .analyzeProgram(); 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 { host, 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', () => { const {renderer} = setup(PROGRAM); const output = new MagicString(PROGRAM.contents); - 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: 'ComponentA2'}, - {from: '/some/foo/b.js', dtsFrom: '/some/foo/b.d.ts', identifier: 'ComponentB'}, - {from: PROGRAM.name, dtsFrom: PROGRAM.name, identifier: 'TopLevelComponent'}, + 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: 'ComponentA2'}, + {from: _('/some/foo/b.js'), dtsFrom: _('/some/foo/b.d.ts'), identifier: 'ComponentB'}, + {from: _(PROGRAM.name), dtsFrom: _(PROGRAM.name), identifier: 'TopLevelComponent'}, ]); expect(output.toString()).toContain(` export {A, B, C, NoIife, BadIife}; @@ -190,11 +192,11 @@ export {TopLevelComponent};`); it('should not insert alias exports in js output', () => { const {renderer} = setup(PROGRAM); const output = new MagicString(PROGRAM.contents); - renderer.addExports(output, PROGRAM.name.replace(/\.js$/, ''), [ - {from: '/some/a.js', alias: 'eComponentA1', identifier: 'ComponentA1'}, - {from: '/some/a.js', alias: 'eComponentA2', identifier: 'ComponentA2'}, - {from: '/some/foo/b.js', alias: 'eComponentB', identifier: 'ComponentB'}, - {from: PROGRAM.name, alias: 'eTopLevelComponent', identifier: 'TopLevelComponent'}, + renderer.addExports(output, _(PROGRAM.name.replace(/\.js$/, '')), [ + {from: _('/some/a.js'), alias: 'eComponentA1', identifier: 'ComponentA1'}, + {from: _('/some/a.js'), alias: 'eComponentA2', identifier: 'ComponentA2'}, + {from: _('/some/foo/b.js'), alias: 'eComponentB', identifier: 'ComponentB'}, + {from: _(PROGRAM.name), alias: 'eTopLevelComponent', identifier: 'TopLevelComponent'}, ]); const outputString = output.toString(); expect(outputString).not.toContain(`{eComponentA1 as ComponentA1}`); diff --git a/packages/compiler-cli/ngcc/test/rendering/renderer_spec.ts b/packages/compiler-cli/ngcc/test/rendering/renderer_spec.ts index 1e9e3bd387..9738585813 100644 --- a/packages/compiler-cli/ngcc/test/rendering/renderer_spec.ts +++ b/packages/compiler-cli/ngcc/test/rendering/renderer_spec.ts @@ -9,6 +9,7 @@ import * as fs from 'fs'; import MagicString from 'magic-string'; import * as ts from 'typescript'; import {fromObject, generateMapFileComment} from 'convert-source-map'; +import {AbsoluteFsPath} from '../../../src/ngtsc/path'; import {CompiledClass, DecorationAnalyzer} from '../../src/analysis/decoration_analyzer'; import {NgccReferencesRegistry} from '../../src/analysis/ngcc_references_registry'; 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 {Logger} from '../../src/logging/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 { constructor( - logger: Logger, host: Esm2015ReflectionHost, isCore: boolean, bundle: EntryPointBundle) { - super(logger, host, isCore, bundle); + fs: FileSystem, logger: Logger, host: Esm2015ReflectionHost, isCore: boolean, + bundle: EntryPointBundle) { + super(fs, logger, host, isCore, bundle); } addImports( output: MagicString, imports: {specifier: string, qualifier: string}[], sf: ts.SourceFile) { @@ -59,8 +65,9 @@ function createTestRenderer( const typeChecker = bundle.src.program.getTypeChecker(); const host = new Esm2015ReflectionHost(logger, isCore, typeChecker, bundle.dts); const referencesRegistry = new NgccReferencesRegistry(host); + const fs = new NodeJSFileSystem(); 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) .analyzeProgram(); const switchMarkerAnalyses = new SwitchMarkerAnalyzer(host).analyzeProgram(bundle.src.program); @@ -68,7 +75,7 @@ function createTestRenderer( new ModuleWithProvidersAnalyzer(host, referencesRegistry).analyzeProgram(bundle.src.program); const privateDeclarationsAnalyses = 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, 'addDefinitions').and.callThrough(); spyOn(renderer, 'removeDecorators').and.callThrough(); @@ -354,7 +361,7 @@ describe('Renderer', () => { // Add a mock export to trigger export rendering 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( decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses, diff --git a/packages/compiler-cli/ngcc/test/writing/in_place_file_writer_spec.ts b/packages/compiler-cli/ngcc/test/writing/in_place_file_writer_spec.ts index 96dcb76d4a..c47474af37 100644 --- a/packages/compiler-cli/ngcc/test/writing/in_place_file_writer_spec.ts +++ b/packages/compiler-cli/ngcc/test/writing/in_place_file_writer_spec.ts @@ -9,10 +9,14 @@ import {existsSync, readFileSync} from '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 {EntryPointBundle} from '../../src/packages/entry_point_bundle'; import {InPlaceFileWriter} from '../../src/writing/in_place_file_writer'; +const _ = AbsoluteFsPath.fromUnchecked; + function createMockFileSystem() { mockFs({ '/package/path': { @@ -39,12 +43,13 @@ describe('InPlaceFileWriter', () => { afterEach(restoreRealFileSystem); 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, [ - {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-2/file-4.js', contents: 'MODIFIED FILE 4'}, - {path: '/package/path/folder-3/file-5.js', contents: 'NEW FILE 5'}, + {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-2/file-4.js'), contents: 'MODIFIED FILE 4'}, + {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/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', () => { - const fileWriter = new InPlaceFileWriter(); + const fs = new NodeJSFileSystem(); + const fileWriter = new InPlaceFileWriter(fs); fileWriter.writeBundle({} as EntryPoint, {} as EntryPointBundle, [ - {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-2/file-4.js', contents: 'MODIFIED FILE 4'}, - {path: '/package/path/folder-3/file-5.js', contents: 'NEW FILE 5'}, + {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-2/file-4.js'), contents: 'MODIFIED FILE 4'}, + {path: _('/package/path/folder-3/file-5.js'), contents: 'NEW FILE 5'}, ]); expect(readFileSync('/package/path/top-level.js.__ivy_ngcc_bak', 'utf8')) .toEqual('ORIGINAL TOP LEVEL'); @@ -74,12 +80,13 @@ describe('InPlaceFileWriter', () => { }); it('should error if the backup file already exists', () => { - const fileWriter = new InPlaceFileWriter(); + const fs = new NodeJSFileSystem(); + const fileWriter = new InPlaceFileWriter(fs); expect( () => fileWriter.writeBundle( {} 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( 'Tried to overwrite /package/path/already-backed-up.js.__ivy_ngcc_bak with an ngcc back up file, which is disallowed.'); diff --git a/packages/compiler-cli/ngcc/test/writing/new_entry_point_file_writer_spec.ts b/packages/compiler-cli/ngcc/test/writing/new_entry_point_file_writer_spec.ts index 60d44a39b2..5775af81ff 100644 --- a/packages/compiler-cli/ngcc/test/writing/new_entry_point_file_writer_spec.ts +++ b/packages/compiler-cli/ngcc/test/writing/new_entry_point_file_writer_spec.ts @@ -10,6 +10,8 @@ import {existsSync, readFileSync} from 'fs'; import * as mockFs from 'mock-fs'; 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 {EntryPointBundle, makeEntryPointBundle} from '../../src/packages/entry_point_bundle'; import {FileWriter} from '../../src/writing/file_writer'; @@ -81,6 +83,7 @@ describe('NewEntryPointFileWriter', () => { beforeEach(createMockFileSystem); afterEach(restoreRealFileSystem); + let fs: FileSystem; let fileWriter: FileWriter; let entryPoint: EntryPoint; let esm5bundle: EntryPointBundle; @@ -88,17 +91,21 @@ describe('NewEntryPointFileWriter', () => { describe('writeBundle() [primary entry-point]', () => { beforeEach(() => { - fileWriter = new NewEntryPointFileWriter(); - entryPoint = - getEntryPointInfo(new MockLogger(), _('/node_modules/test'), _('/node_modules/test')) !; - esm5bundle = makeTestBundle(entryPoint, 'module', 'esm5'); - esm2015bundle = makeTestBundle(entryPoint, 'es2015', 'esm2015'); + fs = new NodeJSFileSystem(); + fileWriter = new NewEntryPointFileWriter(fs); + entryPoint = getEntryPointInfo( + fs, new MockLogger(), _('/node_modules/test'), _('/node_modules/test')) !; + esm5bundle = makeTestBundle(fs, entryPoint, 'module', 'esm5'); + esm2015bundle = makeTestBundle(fs, entryPoint, 'es2015', 'esm2015'); }); it('should write the modified files to a new folder', () => { 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')) .toEqual('export function FooTop() {} // MODIFIED'); @@ -112,7 +119,10 @@ describe('NewEntryPointFileWriter', () => { it('should also copy unmodified files in the program', () => { 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')) .toEqual('export class FooTop {} // MODIFIED'); @@ -126,14 +136,20 @@ describe('NewEntryPointFileWriter', () => { it('should update the package.json properties', () => { 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({ module_ivy_ngcc: '__ivy_ngcc__/esm5.js', })); 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({ module_ivy_ngcc: '__ivy_ngcc__/esm5.js', @@ -144,10 +160,10 @@ describe('NewEntryPointFileWriter', () => { it('should overwrite and backup typings files', () => { fileWriter.writeBundle(entryPoint, esm2015bundle, [ { - path: '/node_modules/test/index.d.ts', - contents: 'export declare class FooTop {} // MODIFIED' + path: _('/node_modules/test/index.d.ts'), + 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')) .toEqual('export declare class FooTop {} // MODIFIED'); @@ -165,16 +181,20 @@ describe('NewEntryPointFileWriter', () => { describe('writeBundle() [secondary entry-point]', () => { beforeEach(() => { - fileWriter = new NewEntryPointFileWriter(); - entryPoint = - getEntryPointInfo(new MockLogger(), _('/node_modules/test'), _('/node_modules/test/a')) !; - esm5bundle = makeTestBundle(entryPoint, 'module', 'esm5'); - esm2015bundle = makeTestBundle(entryPoint, 'es2015', 'esm2015'); + fs = new NodeJSFileSystem(); + fileWriter = new NewEntryPointFileWriter(fs); + entryPoint = getEntryPointInfo( + fs, new MockLogger(), _('/node_modules/test'), _('/node_modules/test/a')) !; + esm5bundle = makeTestBundle(fs, entryPoint, 'module', 'esm5'); + esm2015bundle = makeTestBundle(fs, entryPoint, 'es2015', 'esm2015'); }); it('should write the modified file to a new folder', () => { 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')) .toEqual('export function FooA() {} // MODIFIED'); @@ -184,7 +204,10 @@ describe('NewEntryPointFileWriter', () => { it('should also copy unmodified files in the program', () => { 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')) .toEqual('export class FooA {} // MODIFIED'); @@ -198,14 +221,20 @@ describe('NewEntryPointFileWriter', () => { it('should update the package.json properties', () => { 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({ module_ivy_ngcc: '../__ivy_ngcc__/a/esm5.js', })); 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({ module_ivy_ngcc: '../__ivy_ngcc__/a/esm5.js', @@ -216,7 +245,7 @@ describe('NewEntryPointFileWriter', () => { it('should overwrite and backup typings files', () => { 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' }, ]); @@ -230,16 +259,20 @@ describe('NewEntryPointFileWriter', () => { describe('writeBundle() [entry-point (with files placed outside entry-point folder)]', () => { beforeEach(() => { - fileWriter = new NewEntryPointFileWriter(); - entryPoint = - getEntryPointInfo(new MockLogger(), _('/node_modules/test'), _('/node_modules/test/b')) !; - esm5bundle = makeTestBundle(entryPoint, 'module', 'esm5'); - esm2015bundle = makeTestBundle(entryPoint, 'es2015', 'esm2015'); + fs = new NodeJSFileSystem(); + fileWriter = new NewEntryPointFileWriter(fs); + entryPoint = getEntryPointInfo( + fs, new MockLogger(), _('/node_modules/test'), _('/node_modules/test/b')) !; + esm5bundle = makeTestBundle(fs, entryPoint, 'module', 'esm5'); + esm2015bundle = makeTestBundle(fs, entryPoint, 'es2015', 'esm2015'); }); it('should write the modified file to a new folder', () => { 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')) .toEqual('export function FooB() {} // MODIFIED'); @@ -250,7 +283,7 @@ describe('NewEntryPointFileWriter', () => { it('should also copy unmodified files in the program', () => { 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' }, ]); @@ -278,7 +311,7 @@ describe('NewEntryPointFileWriter', () => { it('should not copy files outside of the package', () => { 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' }, ]); @@ -288,7 +321,10 @@ describe('NewEntryPointFileWriter', () => { it('should update the package.json properties', () => { 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({ module_ivy_ngcc: '../__ivy_ngcc__/lib/esm5.js', @@ -296,7 +332,7 @@ describe('NewEntryPointFileWriter', () => { 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' }, ]); @@ -309,7 +345,7 @@ describe('NewEntryPointFileWriter', () => { it('should overwrite and backup typings files', () => { 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' }, ]); @@ -323,9 +359,9 @@ describe('NewEntryPointFileWriter', () => { }); function makeTestBundle( - entryPoint: EntryPoint, formatProperty: EntryPointJsonProperty, + fs: FileSystem, entryPoint: EntryPoint, formatProperty: EntryPointJsonProperty, format: EntryPointFormat): EntryPointBundle { return makeEntryPointBundle( - entryPoint.path, entryPoint.packageJson[formatProperty] !, entryPoint.typings, false, + fs, entryPoint.path, entryPoint.packageJson[formatProperty] !, entryPoint.typings, false, formatProperty, format, true) !; }