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

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

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

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

View File

@ -6,9 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
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<A, M> {
* Simple class that resolves and loads files directly from the filesystem.
*/
class NgccResourceLoader implements ResourceLoader {
constructor(private fs: FileSystem) {}
canPreload = false;
preload(): undefined|Promise<void> { throw new Error('Not implemented.'); }
load(url: string): string { return fs.readFileSync(url, 'utf8'); }
load(url: string): string { return this.fs.readFile(AbsoluteFsPath.resolve(url)); }
resolve(url: string, containingFile: string): string {
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) {}

View File

@ -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};
});

View File

@ -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<AbsoluteFsPath>, missing: Set<string>,
deepImports: Set<string>, alreadySeen: Set<AbsoluteFsPath>): void {
const fromContents = fs.readFileSync(file, 'utf8');
const fromContents = this.fs.readFile(file);
if (!this.hasImportOrReexportStatements(fromContents)) {
return;
}

View File

@ -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;
}
}

View File

@ -0,0 +1,38 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {AbsoluteFsPath, PathSegment} from '../../../src/ngtsc/path';
/**
* A basic interface to abstract the underlying file-system.
*
* This makes it easier to provide mock file-systems in unit tests,
* but also to create clever file-systems that have features such as caching.
*/
export interface FileSystem {
exists(path: AbsoluteFsPath): boolean;
readFile(path: AbsoluteFsPath): string;
writeFile(path: AbsoluteFsPath, data: string): void;
readdir(path: AbsoluteFsPath): PathSegment[];
lstat(path: AbsoluteFsPath): FileStats;
stat(path: AbsoluteFsPath): FileStats;
pwd(): AbsoluteFsPath;
copyFile(from: AbsoluteFsPath, to: AbsoluteFsPath): void;
moveFile(from: AbsoluteFsPath, to: AbsoluteFsPath): void;
ensureDir(path: AbsoluteFsPath): void;
}
/**
* Information about an object in the FileSystem.
* This is analogous to the `fs.Stats` class in Node.js.
*/
export interface FileStats {
isFile(): boolean;
isDirectory(): boolean;
isSymbolicLink(): boolean;
}

View File

@ -0,0 +1,30 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import * as fs from 'fs';
import {cp, mkdir, mv} from 'shelljs';
import {AbsoluteFsPath, PathSegment} from '../../../src/ngtsc/path';
import {FileSystem} from './file_system';
/**
* A wrapper around the Node.js file-system (i.e the `fs` package).
*/
export class NodeJSFileSystem implements FileSystem {
exists(path: AbsoluteFsPath): boolean { return fs.existsSync(path); }
readFile(path: AbsoluteFsPath): string { return fs.readFileSync(path, 'utf8'); }
writeFile(path: AbsoluteFsPath, data: string): void {
return fs.writeFileSync(path, data, 'utf8');
}
readdir(path: AbsoluteFsPath): PathSegment[] { return fs.readdirSync(path) as PathSegment[]; }
lstat(path: AbsoluteFsPath): fs.Stats { return fs.lstatSync(path); }
stat(path: AbsoluteFsPath): fs.Stats { return fs.statSync(path); }
pwd() { return AbsoluteFsPath.fromUnchecked(process.cwd()); }
copyFile(from: AbsoluteFsPath, to: AbsoluteFsPath): void { cp(from, to); }
moveFile(from: AbsoluteFsPath, to: AbsoluteFsPath): void { mv(from, to); }
ensureDir(path: AbsoluteFsPath): void { mkdir('-p', path); }
}

View File

@ -5,15 +5,13 @@
* Use of this source code is governed by an MIT-style license that can be
* 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);
});
}

View File

@ -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));
}

View File

@ -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;
}

View File

@ -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}.`);

View File

@ -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};

View File

@ -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);
});

View File

@ -0,0 +1,66 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import * as os from 'os';
import * as ts from 'typescript';
import {AbsoluteFsPath} from '../../../src/ngtsc/path';
import {FileSystem} from '../file_system/file_system';
export class NgccCompilerHost implements ts.CompilerHost {
private _caseSensitive = this.fs.exists(AbsoluteFsPath.fromUnchecked(__filename.toUpperCase()));
constructor(private fs: FileSystem, private options: ts.CompilerOptions) {}
getSourceFile(fileName: string, languageVersion: ts.ScriptTarget): ts.SourceFile|undefined {
const text = this.readFile(fileName);
return text !== undefined ? ts.createSourceFile(fileName, text, languageVersion) : undefined;
}
getDefaultLibFileName(options: ts.CompilerOptions): string {
return this.getDefaultLibLocation() + '/' + ts.getDefaultLibFileName(options);
}
getDefaultLibLocation(): string {
const nodeLibPath = AbsoluteFsPath.fromUnchecked(require.resolve('typescript'));
return AbsoluteFsPath.join(nodeLibPath, '..');
}
writeFile(fileName: string, data: string): void {
this.fs.writeFile(AbsoluteFsPath.fromUnchecked(fileName), data);
}
getCurrentDirectory(): string { return this.fs.pwd(); }
getCanonicalFileName(fileName: string): string {
return this.useCaseSensitiveFileNames ? fileName : fileName.toLowerCase();
}
useCaseSensitiveFileNames(): boolean { return this._caseSensitive; }
getNewLine(): string {
switch (this.options.newLine) {
case ts.NewLineKind.CarriageReturnLineFeed:
return '\r\n';
case ts.NewLineKind.LineFeed:
return '\n';
default:
return os.EOL;
}
}
fileExists(fileName: string): boolean {
return this.fs.exists(AbsoluteFsPath.fromUnchecked(fileName));
}
readFile(fileName: string): string|undefined {
if (!this.fileExists(fileName)) {
return undefined;
}
return this.fs.readFile(AbsoluteFsPath.fromUnchecked(fileName));
}
}

View File

@ -12,6 +12,7 @@ import {ModuleWithProvidersAnalyses, ModuleWithProvidersAnalyzer} from '../analy
import {NgccReferencesRegistry} from '../analysis/ngcc_references_registry';
import {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 =

View File

@ -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);
}
/**

View File

@ -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}'` : '';
}

View File

@ -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<T extends string>(filePath: T): T {
return filePath.replace(/\.(js|d\.ts)$/, '') as T;
}
/**

View File

@ -6,15 +6,11 @@
* Use of this source code is governed by an MIT-style license that can be
* 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);
}
}

View File

@ -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));
}
}

View File

@ -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];

View File

@ -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',
}]);

View File

@ -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';

View File

@ -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()', () => {

View File

@ -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);

View File

@ -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')))

View File

@ -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};
}

View File

@ -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<CtorParameter>({
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<CtorParameter>({
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<CtorParameter>({
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<CtorParameter>({
name: 'arg1',
decorators: null,
}));
expect(parameters ![1]).toEqual(jasmine.objectContaining({
expect(parameters ![1]).toEqual(jasmine.objectContaining<CtorParameter>({
name: 'arg2',
decorators: jasmine.any(Array) as any
}));

View File

@ -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));
}

View File

@ -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');
});
});

View File

@ -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);

View File

@ -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);
});
});

View File

@ -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}`);

View File

@ -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}`);

View File

@ -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,

View File

@ -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.');

View File

@ -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) !;
}