refactor(ivy): ngcc - add MockFileSystem (#29643)

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

View File

@ -12,14 +12,16 @@ import {Decorator} from '../../../src/ngtsc/reflection';
import {DecoratorHandler, DetectResult} from '../../../src/ngtsc/transform'; import {DecoratorHandler, DetectResult} from '../../../src/ngtsc/transform';
import {CompiledClass, DecorationAnalyses, DecorationAnalyzer} from '../../src/analysis/decoration_analyzer'; import {CompiledClass, DecorationAnalyses, DecorationAnalyzer} from '../../src/analysis/decoration_analyzer';
import {NgccReferencesRegistry} from '../../src/analysis/ngcc_references_registry'; import {NgccReferencesRegistry} from '../../src/analysis/ngcc_references_registry';
import {NodeJSFileSystem} from '../../src/file_system/node_js_file_system';
import {Esm2015ReflectionHost} from '../../src/host/esm2015_host'; import {Esm2015ReflectionHost} from '../../src/host/esm2015_host';
import {Folder, MockFileSystem} from '../helpers/mock_file_system';
import {MockLogger} from '../helpers/mock_logger'; import {MockLogger} from '../helpers/mock_logger';
import {makeTestBundleProgram} from '../helpers/utils'; import {createFileSystemFromProgramFiles, makeTestBundleProgram} from '../helpers/utils';
const _ = AbsoluteFsPath.fromUnchecked;
const TEST_PROGRAM = [ const TEST_PROGRAM = [
{ {
name: 'test.js', name: _('/test.js'),
contents: ` contents: `
import {Component, Directive, Injectable} from '@angular/core'; import {Component, Directive, Injectable} from '@angular/core';
@ -34,7 +36,7 @@ const TEST_PROGRAM = [
`, `,
}, },
{ {
name: 'other.js', name: _('/other.js'),
contents: ` contents: `
import {Component} from '@angular/core'; import {Component} from '@angular/core';
@ -46,7 +48,7 @@ const TEST_PROGRAM = [
const INTERNAL_COMPONENT_PROGRAM = [ const INTERNAL_COMPONENT_PROGRAM = [
{ {
name: 'entrypoint.js', name: _('/entrypoint.js'),
contents: ` contents: `
import {Component, NgModule} from '@angular/core'; import {Component, NgModule} from '@angular/core';
import {ImportedComponent} from './component'; import {ImportedComponent} from './component';
@ -62,7 +64,7 @@ const INTERNAL_COMPONENT_PROGRAM = [
` `
}, },
{ {
name: 'component.js', name: _('/component.js'),
contents: ` contents: `
import {Component} from '@angular/core'; import {Component} from '@angular/core';
export class ImportedComponent {} export class ImportedComponent {}
@ -137,7 +139,7 @@ describe('DecorationAnalyzer', () => {
const reflectionHost = const reflectionHost =
new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker()); new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker());
const referencesRegistry = new NgccReferencesRegistry(reflectionHost); const referencesRegistry = new NgccReferencesRegistry(reflectionHost);
const fs = new NodeJSFileSystem(); const fs = new MockFileSystem(createFileSystemFromProgramFiles(...progArgs));
const analyzer = new DecorationAnalyzer( const analyzer = new DecorationAnalyzer(
fs, program, options, host, program.getTypeChecker(), reflectionHost, referencesRegistry, fs, program, options, host, program.getTypeChecker(), reflectionHost, referencesRegistry,
[AbsoluteFsPath.fromUnchecked('/')], false); [AbsoluteFsPath.fromUnchecked('/')], false);

View File

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

View File

@ -5,27 +5,23 @@
* Use of this source code is governed by an MIT-style license that can be * Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import * as mockFs from 'mock-fs';
import * as ts from 'typescript'; import * as ts from 'typescript';
import {AbsoluteFsPath} from '../../../src/ngtsc/path'; import {AbsoluteFsPath} from '../../../src/ngtsc/path';
import {EsmDependencyHost} from '../../src/dependencies/esm_dependency_host'; import {EsmDependencyHost} from '../../src/dependencies/esm_dependency_host';
import {ModuleResolver} from '../../src/dependencies/module_resolver'; import {ModuleResolver} from '../../src/dependencies/module_resolver';
import {NodeJSFileSystem} from '../../src/file_system/node_js_file_system'; import {MockFileSystem} from '../helpers/mock_file_system';
const _ = AbsoluteFsPath.from; const _ = AbsoluteFsPath.from;
describe('DependencyHost', () => { describe('DependencyHost', () => {
let host: EsmDependencyHost; let host: EsmDependencyHost;
beforeEach(() => { beforeEach(() => {
const fs = new NodeJSFileSystem(); const fs = createMockFileSystem();
host = new EsmDependencyHost(fs, new ModuleResolver(fs)); host = new EsmDependencyHost(fs, new ModuleResolver(fs));
}); });
describe('getDependencies()', () => { describe('getDependencies()', () => {
beforeEach(createMockFileSystem);
afterEach(restoreRealFileSystem);
it('should not generate a TS AST if the source does not contain any imports or re-exports', it('should not generate a TS AST if the source does not contain any imports or re-exports',
() => { () => {
spyOn(ts, 'createSourceFile'); spyOn(ts, 'createSourceFile');
@ -98,7 +94,7 @@ describe('DependencyHost', () => {
}); });
it('should support `paths` alias mappings when resolving modules', () => { it('should support `paths` alias mappings when resolving modules', () => {
const fs = new NodeJSFileSystem(); const fs = createMockFileSystem();
host = new EsmDependencyHost(fs, new ModuleResolver(fs, { host = new EsmDependencyHost(fs, new ModuleResolver(fs, {
baseUrl: '/dist', baseUrl: '/dist',
paths: { paths: {
@ -115,9 +111,10 @@ describe('DependencyHost', () => {
expect(missing.size).toBe(0); expect(missing.size).toBe(0);
expect(deepImports.size).toBe(0); expect(deepImports.size).toBe(0);
}); });
});
function createMockFileSystem() { function createMockFileSystem() {
mockFs({ return new MockFileSystem({
'/no/imports/or/re-exports/index.js': '// some text but no import-like statements', '/no/imports/or/re-exports/index.js': '// some text but no import-like statements',
'/no/imports/or/re-exports/package.json': '{"esm2015": "./index.js"}', '/no/imports/or/re-exports/package.json': '{"esm2015": "./index.js"}',
'/no/imports/or/re-exports/index.metadata.json': 'MOCK METADATA', '/no/imports/or/re-exports/index.metadata.json': 'MOCK METADATA',
@ -127,8 +124,7 @@ describe('DependencyHost', () => {
'/external/re-exports/index.js': `export {X} from 'lib-1';\nexport {Y} from 'lib-1/sub-1';`, '/external/re-exports/index.js': `export {X} from 'lib-1';\nexport {Y} from 'lib-1/sub-1';`,
'/external/re-exports/package.json': '{"esm2015": "./index.js"}', '/external/re-exports/package.json': '{"esm2015": "./index.js"}',
'/external/re-exports/index.metadata.json': 'MOCK METADATA', '/external/re-exports/index.metadata.json': 'MOCK METADATA',
'/external/imports-missing/index.js': '/external/imports-missing/index.js': `import {X} from 'lib-1';\nimport {Y} from 'missing';`,
`import {X} from 'lib-1';\nimport {Y} from 'missing';`,
'/external/imports-missing/package.json': '{"esm2015": "./index.js"}', '/external/imports-missing/package.json': '{"esm2015": "./index.js"}',
'/external/imports-missing/index.metadata.json': 'MOCK METADATA', '/external/imports-missing/index.metadata.json': 'MOCK METADATA',
'/external/deep-import/index.js': `import {Y} from 'lib-1/deep/import';`, '/external/deep-import/index.js': `import {Y} from 'lib-1/deep/import';`,
@ -174,9 +170,6 @@ describe('DependencyHost', () => {
}); });
} }
function restoreRealFileSystem() { mockFs.restore(); }
});
describe('isStringImportOrReexport', () => { describe('isStringImportOrReexport', () => {
it('should return true if the statement is an import', () => { it('should return true if the statement is an import', () => {
expect(host.isStringImportOrReexport(createStatement('import {X} from "some/x";'))) expect(host.isStringImportOrReexport(createStatement('import {X} from "some/x";')))

View File

@ -5,17 +5,14 @@
* Use of this source code is governed by an MIT-style license that can be * Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import * as mockFs from 'mock-fs';
import {AbsoluteFsPath} from '../../../src/ngtsc/path'; import {AbsoluteFsPath} from '../../../src/ngtsc/path';
import {ModuleResolver, ResolvedDeepImport, ResolvedExternalModule, ResolvedRelativeModule} from '../../src/dependencies/module_resolver'; import {ModuleResolver, ResolvedDeepImport, ResolvedExternalModule, ResolvedRelativeModule} from '../../src/dependencies/module_resolver';
import {NodeJSFileSystem} from '../../src/file_system/node_js_file_system'; import {MockFileSystem} from '../helpers/mock_file_system';
const _ = AbsoluteFsPath.from; const _ = AbsoluteFsPath.from;
function createMockFileSystem() { function createMockFileSystem() {
mockFs({ return new MockFileSystem({
'/libs': { '/libs': {
'local-package': { 'local-package': {
'package.json': 'PACKAGE.JSON for local-package', 'package.json': 'PACKAGE.JSON for local-package',
@ -68,19 +65,12 @@ function createMockFileSystem() {
}); });
} }
function restoreRealFileSystem() {
mockFs.restore();
}
describe('ModuleResolver', () => { describe('ModuleResolver', () => {
beforeEach(createMockFileSystem);
afterEach(restoreRealFileSystem);
describe('resolveModule()', () => { describe('resolveModule()', () => {
describe('with relative paths', () => { describe('with relative paths', () => {
it('should resolve sibling, child and aunt modules', () => { it('should resolve sibling, child and aunt modules', () => {
const fs = new NodeJSFileSystem(); const resolver = new ModuleResolver(createMockFileSystem());
const resolver = new ModuleResolver(fs);
expect(resolver.resolveModuleImport('./x', _('/libs/local-package/index.js'))) expect(resolver.resolveModuleImport('./x', _('/libs/local-package/index.js')))
.toEqual(new ResolvedRelativeModule(_('/libs/local-package/x.js'))); .toEqual(new ResolvedRelativeModule(_('/libs/local-package/x.js')));
expect(resolver.resolveModuleImport('./sub-folder', _('/libs/local-package/index.js'))) expect(resolver.resolveModuleImport('./sub-folder', _('/libs/local-package/index.js')))
@ -90,16 +80,14 @@ describe('ModuleResolver', () => {
}); });
it('should return `null` if the resolved module relative module does not exist', () => { it('should return `null` if the resolved module relative module does not exist', () => {
const fs = new NodeJSFileSystem(); const resolver = new ModuleResolver(createMockFileSystem());
const resolver = new ModuleResolver(fs);
expect(resolver.resolveModuleImport('./y', _('/libs/local-package/index.js'))).toBe(null); expect(resolver.resolveModuleImport('./y', _('/libs/local-package/index.js'))).toBe(null);
}); });
}); });
describe('with non-mapped external paths', () => { describe('with non-mapped external paths', () => {
it('should resolve to the package.json of a local node_modules package', () => { it('should resolve to the package.json of a local node_modules package', () => {
const fs = new NodeJSFileSystem(); const resolver = new ModuleResolver(createMockFileSystem());
const resolver = new ModuleResolver(fs);
expect(resolver.resolveModuleImport('package-1', _('/libs/local-package/index.js'))) expect(resolver.resolveModuleImport('package-1', _('/libs/local-package/index.js')))
.toEqual(new ResolvedExternalModule(_('/libs/local-package/node_modules/package-1'))); .toEqual(new ResolvedExternalModule(_('/libs/local-package/node_modules/package-1')));
expect( expect(
@ -110,8 +98,7 @@ describe('ModuleResolver', () => {
}); });
it('should resolve to the package.json of a higher node_modules package', () => { it('should resolve to the package.json of a higher node_modules package', () => {
const fs = new NodeJSFileSystem(); const resolver = new ModuleResolver(createMockFileSystem());
const resolver = new ModuleResolver(fs);
expect(resolver.resolveModuleImport('package-2', _('/libs/local-package/index.js'))) expect(resolver.resolveModuleImport('package-2', _('/libs/local-package/index.js')))
.toEqual(new ResolvedExternalModule(_('/libs/node_modules/package-2'))); .toEqual(new ResolvedExternalModule(_('/libs/node_modules/package-2')));
expect(resolver.resolveModuleImport('top-package', _('/libs/local-package/index.js'))) expect(resolver.resolveModuleImport('top-package', _('/libs/local-package/index.js')))
@ -119,23 +106,20 @@ describe('ModuleResolver', () => {
}); });
it('should return `null` if the package cannot be found', () => { it('should return `null` if the package cannot be found', () => {
const fs = new NodeJSFileSystem(); const resolver = new ModuleResolver(createMockFileSystem());
const resolver = new ModuleResolver(fs);
expect(resolver.resolveModuleImport('missing-2', _('/libs/local-package/index.js'))) expect(resolver.resolveModuleImport('missing-2', _('/libs/local-package/index.js')))
.toBe(null); .toBe(null);
}); });
it('should return `null` if the package is not accessible because it is in a inner node_modules package', it('should return `null` if the package is not accessible because it is in a inner node_modules package',
() => { () => {
const fs = new NodeJSFileSystem(); const resolver = new ModuleResolver(createMockFileSystem());
const resolver = new ModuleResolver(fs);
expect(resolver.resolveModuleImport('package-3', _('/libs/local-package/index.js'))) expect(resolver.resolveModuleImport('package-3', _('/libs/local-package/index.js')))
.toBe(null); .toBe(null);
}); });
it('should identify deep imports into an external module', () => { it('should identify deep imports into an external module', () => {
const fs = new NodeJSFileSystem(); const resolver = new ModuleResolver(createMockFileSystem());
const resolver = new ModuleResolver(fs);
expect( expect(
resolver.resolveModuleImport('package-1/sub-folder', _('/libs/local-package/index.js'))) resolver.resolveModuleImport('package-1/sub-folder', _('/libs/local-package/index.js')))
.toEqual( .toEqual(
@ -145,9 +129,8 @@ describe('ModuleResolver', () => {
describe('with mapped path external modules', () => { describe('with mapped path external modules', () => {
it('should resolve to the package.json of simple mapped packages', () => { it('should resolve to the package.json of simple mapped packages', () => {
const fs = new NodeJSFileSystem(); const resolver = new ModuleResolver(
const resolver = createMockFileSystem(), {baseUrl: '/dist', paths: {'*': ['*', 'sub-folder/*']}});
new ModuleResolver(fs, {baseUrl: '/dist', paths: {'*': ['*', 'sub-folder/*']}});
expect(resolver.resolveModuleImport('package-4', _('/libs/local-package/index.js'))) expect(resolver.resolveModuleImport('package-4', _('/libs/local-package/index.js')))
.toEqual(new ResolvedExternalModule(_('/dist/package-4'))); .toEqual(new ResolvedExternalModule(_('/dist/package-4')));
@ -157,8 +140,7 @@ describe('ModuleResolver', () => {
}); });
it('should select the best match by the length of prefix before the *', () => { it('should select the best match by the length of prefix before the *', () => {
const fs = new NodeJSFileSystem(); const resolver = new ModuleResolver(createMockFileSystem(), {
const resolver = new ModuleResolver(fs, {
baseUrl: '/dist', baseUrl: '/dist',
paths: { paths: {
'@lib/*': ['*'], '@lib/*': ['*'],
@ -176,7 +158,7 @@ describe('ModuleResolver', () => {
it('should follow the ordering of `paths` when matching mapped packages', () => { it('should follow the ordering of `paths` when matching mapped packages', () => {
let resolver: ModuleResolver; let resolver: ModuleResolver;
const fs = new NodeJSFileSystem(); const fs = createMockFileSystem();
resolver = new ModuleResolver(fs, {baseUrl: '/dist', paths: {'*': ['*', 'sub-folder/*']}}); resolver = new ModuleResolver(fs, {baseUrl: '/dist', paths: {'*': ['*', 'sub-folder/*']}});
expect(resolver.resolveModuleImport('package-4', _('/libs/local-package/index.js'))) expect(resolver.resolveModuleImport('package-4', _('/libs/local-package/index.js')))
.toEqual(new ResolvedExternalModule(_('/dist/package-4'))); .toEqual(new ResolvedExternalModule(_('/dist/package-4')));
@ -187,17 +169,15 @@ describe('ModuleResolver', () => {
}); });
it('should resolve packages when the path mappings have post-fixes', () => { it('should resolve packages when the path mappings have post-fixes', () => {
const fs = new NodeJSFileSystem(); const resolver = new ModuleResolver(
const resolver = createMockFileSystem(), {baseUrl: '/dist', paths: {'*': ['sub-folder/*/post-fix']}});
new ModuleResolver(fs, {baseUrl: '/dist', paths: {'*': ['sub-folder/*/post-fix']}});
expect(resolver.resolveModuleImport('package-5', _('/libs/local-package/index.js'))) expect(resolver.resolveModuleImport('package-5', _('/libs/local-package/index.js')))
.toEqual(new ResolvedExternalModule(_('/dist/sub-folder/package-5/post-fix'))); .toEqual(new ResolvedExternalModule(_('/dist/sub-folder/package-5/post-fix')));
}); });
it('should match paths against complex path matchers', () => { it('should match paths against complex path matchers', () => {
const fs = new NodeJSFileSystem(); const resolver = new ModuleResolver(
const resolver = createMockFileSystem(), {baseUrl: '/dist', paths: {'@shared/*': ['sub-folder/*']}});
new ModuleResolver(fs, {baseUrl: '/dist', paths: {'@shared/*': ['sub-folder/*']}});
expect(resolver.resolveModuleImport('@shared/package-4', _('/libs/local-package/index.js'))) expect(resolver.resolveModuleImport('@shared/package-4', _('/libs/local-package/index.js')))
.toEqual(new ResolvedExternalModule(_('/dist/sub-folder/package-4'))); .toEqual(new ResolvedExternalModule(_('/dist/sub-folder/package-4')));
expect(resolver.resolveModuleImport('package-5', _('/libs/local-package/index.js'))) expect(resolver.resolveModuleImport('package-5', _('/libs/local-package/index.js')))
@ -206,17 +186,17 @@ describe('ModuleResolver', () => {
it('should resolve path as "relative" if the mapped path is inside the current package', it('should resolve path as "relative" if the mapped path is inside the current package',
() => { () => {
const fs = new NodeJSFileSystem(); const resolver = new ModuleResolver(
const resolver = new ModuleResolver(fs, {baseUrl: '/dist', paths: {'@shared/*': ['*']}}); createMockFileSystem(), {baseUrl: '/dist', paths: {'@shared/*': ['*']}});
expect(resolver.resolveModuleImport( expect(resolver.resolveModuleImport(
'@shared/package-4/x', _('/dist/package-4/sub-folder/index.js'))) '@shared/package-4/x', _('/dist/package-4/sub-folder/index.js')))
.toEqual(new ResolvedRelativeModule(_('/dist/package-4/x.js'))); .toEqual(new ResolvedRelativeModule(_('/dist/package-4/x.js')));
}); });
it('should resolve paths where the wildcard matches more than one path segment', () => { it('should resolve paths where the wildcard matches more than one path segment', () => {
const fs = new NodeJSFileSystem();
const resolver = new ModuleResolver( const resolver = new ModuleResolver(
fs, {baseUrl: '/dist', paths: {'@shared/*/post-fix': ['*/post-fix']}}); createMockFileSystem(),
{baseUrl: '/dist', paths: {'@shared/*/post-fix': ['*/post-fix']}});
expect( expect(
resolver.resolveModuleImport( resolver.resolveModuleImport(
'@shared/sub-folder/package-5/post-fix', _('/dist/package-4/sub-folder/index.js'))) '@shared/sub-folder/package-5/post-fix', _('/dist/package-4/sub-folder/index.js')))

View File

@ -0,0 +1,173 @@
/**
* @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';
import {FileStats, FileSystem} from '../../src/file_system/file_system';
/**
* An in-memory file system that can be used in unit tests.
*/
export class MockFileSystem implements FileSystem {
files: Folder = {};
constructor(...folders: Folder[]) {
folders.forEach(files => this.processFiles(this.files, files));
}
exists(path: AbsoluteFsPath): boolean { return this.findFromPath(path) !== null; }
readFile(path: AbsoluteFsPath): string {
const file = this.findFromPath(path);
if (isFile(file)) {
return file;
} else {
throw new MockFileSystemError('ENOENT', path, `File "${path}" does not exist.`);
}
}
writeFile(path: AbsoluteFsPath, data: string): void {
const [folderPath, basename] = this.splitIntoFolderAndFile(path);
const folder = this.findFromPath(folderPath);
if (!isFolder(folder)) {
throw new MockFileSystemError(
'ENOENT', path, `Unable to write file "${path}". The containing folder does not exist.`);
}
folder[basename] = data;
}
readdir(path: AbsoluteFsPath): PathSegment[] {
const folder = this.findFromPath(path);
if (folder === null) {
throw new MockFileSystemError(
'ENOENT', path, `Unable to read directory "${path}". It does not exist.`);
}
if (isFile(folder)) {
throw new MockFileSystemError(
'ENOTDIR', path, `Unable to read directory "${path}". It is a file.`);
}
return Object.keys(folder) as PathSegment[];
}
lstat(path: AbsoluteFsPath): FileStats {
const fileOrFolder = this.findFromPath(path);
if (fileOrFolder === null) {
throw new MockFileSystemError('ENOENT', path, `File "${path}" does not exist.`);
}
return new MockFileStats(fileOrFolder);
}
stat(path: AbsoluteFsPath): FileStats {
const fileOrFolder = this.findFromPath(path, {followSymLinks: true});
if (fileOrFolder === null) {
throw new MockFileSystemError('ENOENT', path, `File "${path}" does not exist.`);
}
return new MockFileStats(fileOrFolder);
}
pwd(): AbsoluteFsPath { return AbsoluteFsPath.fromUnchecked('/'); }
copyFile(from: AbsoluteFsPath, to: AbsoluteFsPath): void {
this.writeFile(to, this.readFile(from));
}
moveFile(from: AbsoluteFsPath, to: AbsoluteFsPath): void {
this.writeFile(to, this.readFile(from));
const folder = this.findFromPath(AbsoluteFsPath.dirname(from)) as Folder;
const basename = PathSegment.basename(from);
delete folder[basename];
}
ensureDir(path: AbsoluteFsPath): void { this.ensureFolders(this.files, path.split('/')); }
private processFiles(current: Folder, files: Folder): void {
Object.keys(files).forEach(path => {
const segments = path.split('/');
const lastSegment = segments.pop() !;
const containingFolder = this.ensureFolders(current, segments);
const entity = files[path];
if (isFolder(entity)) {
const processedFolder = containingFolder[lastSegment] = {} as Folder;
this.processFiles(processedFolder, entity);
} else {
containingFolder[lastSegment] = entity;
}
});
}
private ensureFolders(current: Folder, segments: string[]): Folder {
for (const segment of segments) {
if (isFile(current[segment])) {
throw new Error(`Folder already exists as a file.`);
}
if (!current[segment]) {
current[segment] = {};
}
current = current[segment] as Folder;
}
return current;
}
private findFromPath(path: AbsoluteFsPath, options?: {followSymLinks: boolean}): Entity|null {
const followSymLinks = !!options && options.followSymLinks;
const segments = path.split('/');
let current = this.files;
while (segments.length) {
const next: Entity = current[segments.shift() !];
if (next === undefined) {
return null;
}
if (segments.length > 0 && (!isFolder(next))) {
return null;
}
if (isFile(next)) {
return next;
}
if (isSymLink(next)) {
return followSymLinks ?
this.findFromPath(AbsoluteFsPath.resolve(next.path, ...segments), {followSymLinks}) :
next;
}
current = next;
}
return current || null;
}
private splitIntoFolderAndFile(path: AbsoluteFsPath): [AbsoluteFsPath, string] {
const segments = path.split('/');
const file = segments.pop() !;
return [AbsoluteFsPath.fromUnchecked(segments.join('/')), file];
}
}
export type Entity = Folder | File | SymLink;
export interface Folder { [pathSegments: string]: Entity; }
export type File = string;
export class SymLink {
constructor(public path: AbsoluteFsPath) {}
}
class MockFileStats implements FileStats {
constructor(private entity: Entity) {}
isFile(): boolean { return isFile(this.entity); }
isDirectory(): boolean { return isFolder(this.entity); }
isSymbolicLink(): boolean { return isSymLink(this.entity); }
}
class MockFileSystemError extends Error {
constructor(public code: string, public path: string, message: string) { super(message); }
}
function isFile(item: Entity | null): item is File {
return typeof item === 'string';
}
function isSymLink(item: Entity | null): item is SymLink {
return item instanceof SymLink;
}
function isFolder(item: Entity | null): item is Folder {
return item !== null && !isFile(item) && !isSymLink(item);
}

View File

@ -12,6 +12,7 @@ import {makeProgram} from '../../../src/ngtsc/testing/in_memory_typescript';
import {BundleProgram} from '../../src/packages/bundle_program'; import {BundleProgram} from '../../src/packages/bundle_program';
import {EntryPointFormat, EntryPointJsonProperty} from '../../src/packages/entry_point'; import {EntryPointFormat, EntryPointJsonProperty} from '../../src/packages/entry_point';
import {EntryPointBundle} from '../../src/packages/entry_point_bundle'; import {EntryPointBundle} from '../../src/packages/entry_point_bundle';
import {Folder} from './mock_file_system';
export {getDeclaration} from '../../../src/ngtsc/testing/in_memory_typescript'; export {getDeclaration} from '../../../src/ngtsc/testing/in_memory_typescript';
@ -121,3 +122,11 @@ export function convertToDirectTsLibImport(filesystem: {name: string, contents:
return {...file, contents}; return {...file, contents};
}); });
} }
export function createFileSystemFromProgramFiles(
...fileCollections: ({name: string, contents: string}[] | undefined)[]): Folder {
const folder: Folder = {};
fileCollections.forEach(
files => files && files.forEach(file => folder[file.name] = file.contents));
return folder;
}

View File

@ -5,16 +5,12 @@
* Use of this source code is governed by an MIT-style license that can be * Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {readFileSync} from 'fs';
import * as mockFs from 'mock-fs';
import {AbsoluteFsPath} from '../../../src/ngtsc/path'; import {AbsoluteFsPath} from '../../../src/ngtsc/path';
import {NodeJSFileSystem} from '../../src/file_system/node_js_file_system';
import {hasBeenProcessed, markAsProcessed} from '../../src/packages/build_marker'; import {hasBeenProcessed, markAsProcessed} from '../../src/packages/build_marker';
import {MockFileSystem} from '../helpers/mock_file_system';
function createMockFileSystem() { function createMockFileSystem() {
mockFs({ return new MockFileSystem({
'/node_modules/@angular/common': { '/node_modules/@angular/common': {
'package.json': `{ 'package.json': `{
"fesm2015": "./fesm2015/common.js", "fesm2015": "./fesm2015/common.js",
@ -90,38 +86,31 @@ function createMockFileSystem() {
}); });
} }
function restoreRealFileSystem() {
mockFs.restore();
}
describe('Marker files', () => { describe('Marker files', () => {
beforeEach(createMockFileSystem);
afterEach(restoreRealFileSystem);
const COMMON_PACKAGE_PATH = AbsoluteFsPath.from('/node_modules/@angular/common/package.json'); const COMMON_PACKAGE_PATH = AbsoluteFsPath.from('/node_modules/@angular/common/package.json');
describe('markAsProcessed', () => { describe('markAsProcessed', () => {
it('should write a property in the package.json containing the version placeholder', () => { it('should write a property in the package.json containing the version placeholder', () => {
let pkg = JSON.parse(readFileSync(COMMON_PACKAGE_PATH, 'utf8')); const fs = createMockFileSystem();
expect(pkg.__processed_by_ivy_ngcc__).toBeUndefined();
expect(pkg.__processed_by_ivy_ngcc__).toBeUndefined();
const fs = new NodeJSFileSystem(); let pkg = JSON.parse(fs.readFile(COMMON_PACKAGE_PATH));
expect(pkg.__processed_by_ivy_ngcc__).toBeUndefined();
expect(pkg.__processed_by_ivy_ngcc__).toBeUndefined();
markAsProcessed(fs, pkg, COMMON_PACKAGE_PATH, 'fesm2015'); markAsProcessed(fs, pkg, COMMON_PACKAGE_PATH, 'fesm2015');
pkg = JSON.parse(readFileSync(COMMON_PACKAGE_PATH, 'utf8')); pkg = JSON.parse(fs.readFile(COMMON_PACKAGE_PATH));
expect(pkg.__processed_by_ivy_ngcc__.fesm2015).toEqual('0.0.0-PLACEHOLDER'); expect(pkg.__processed_by_ivy_ngcc__.fesm2015).toEqual('0.0.0-PLACEHOLDER');
expect(pkg.__processed_by_ivy_ngcc__.esm5).toBeUndefined(); expect(pkg.__processed_by_ivy_ngcc__.esm5).toBeUndefined();
markAsProcessed(fs, pkg, COMMON_PACKAGE_PATH, 'esm5'); markAsProcessed(fs, pkg, COMMON_PACKAGE_PATH, 'esm5');
pkg = JSON.parse(readFileSync(COMMON_PACKAGE_PATH, 'utf8')); pkg = JSON.parse(fs.readFile(COMMON_PACKAGE_PATH));
expect(pkg.__processed_by_ivy_ngcc__.fesm2015).toEqual('0.0.0-PLACEHOLDER'); expect(pkg.__processed_by_ivy_ngcc__.fesm2015).toEqual('0.0.0-PLACEHOLDER');
expect(pkg.__processed_by_ivy_ngcc__.esm5).toEqual('0.0.0-PLACEHOLDER'); expect(pkg.__processed_by_ivy_ngcc__.esm5).toEqual('0.0.0-PLACEHOLDER');
}); });
it('should update the packageJson object in-place', () => { it('should update the packageJson object in-place', () => {
const fs = new NodeJSFileSystem(); const fs = createMockFileSystem();
let pkg = JSON.parse(readFileSync(COMMON_PACKAGE_PATH, 'utf8')); let pkg = JSON.parse(fs.readFile(COMMON_PACKAGE_PATH));
expect(pkg.__processed_by_ivy_ngcc__).toBeUndefined(); expect(pkg.__processed_by_ivy_ngcc__).toBeUndefined();
markAsProcessed(fs, pkg, COMMON_PACKAGE_PATH, 'fesm2015'); markAsProcessed(fs, pkg, COMMON_PACKAGE_PATH, 'fesm2015');
expect(pkg.__processed_by_ivy_ngcc__.fesm2015).toEqual('0.0.0-PLACEHOLDER'); expect(pkg.__processed_by_ivy_ngcc__.fesm2015).toEqual('0.0.0-PLACEHOLDER');

View File

@ -6,15 +6,13 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import * as mockFs from 'mock-fs';
import {AbsoluteFsPath} from '../../../src/ngtsc/path'; import {AbsoluteFsPath} from '../../../src/ngtsc/path';
import {DependencyResolver} from '../../src/dependencies/dependency_resolver'; import {DependencyResolver} from '../../src/dependencies/dependency_resolver';
import {EsmDependencyHost} from '../../src/dependencies/esm_dependency_host'; import {EsmDependencyHost} from '../../src/dependencies/esm_dependency_host';
import {ModuleResolver} from '../../src/dependencies/module_resolver'; import {ModuleResolver} from '../../src/dependencies/module_resolver';
import {NodeJSFileSystem} from '../../src/file_system/node_js_file_system';
import {EntryPoint} from '../../src/packages/entry_point'; import {EntryPoint} from '../../src/packages/entry_point';
import {EntryPointFinder} from '../../src/packages/entry_point_finder'; import {EntryPointFinder} from '../../src/packages/entry_point_finder';
import {MockFileSystem, SymLink} from '../helpers/mock_file_system';
import {MockLogger} from '../helpers/mock_logger'; import {MockLogger} from '../helpers/mock_logger';
const _ = AbsoluteFsPath.from; const _ = AbsoluteFsPath.from;
@ -23,7 +21,7 @@ describe('findEntryPoints()', () => {
let resolver: DependencyResolver; let resolver: DependencyResolver;
let finder: EntryPointFinder; let finder: EntryPointFinder;
beforeEach(() => { beforeEach(() => {
const fs = new NodeJSFileSystem(); const fs = createMockFileSystem();
resolver = resolver =
new DependencyResolver(new MockLogger(), new EsmDependencyHost(fs, new ModuleResolver(fs))); new DependencyResolver(new MockLogger(), new EsmDependencyHost(fs, new ModuleResolver(fs)));
spyOn(resolver, 'sortEntryPointsByDependency').and.callFake((entryPoints: EntryPoint[]) => { spyOn(resolver, 'sortEntryPointsByDependency').and.callFake((entryPoints: EntryPoint[]) => {
@ -31,8 +29,6 @@ describe('findEntryPoints()', () => {
}); });
finder = new EntryPointFinder(fs, new MockLogger(), resolver); finder = new EntryPointFinder(fs, new MockLogger(), resolver);
}); });
beforeEach(createMockFileSystem);
afterEach(restoreRealFileSystem);
it('should find sub-entry-points within a package', () => { it('should find sub-entry-points within a package', () => {
const {entryPoints} = finder.findEntryPoints(_('/sub_entry_points')); const {entryPoints} = finder.findEntryPoints(_('/sub_entry_points'));
@ -90,7 +86,7 @@ describe('findEntryPoints()', () => {
}); });
function createMockFileSystem() { function createMockFileSystem() {
mockFs({ return new MockFileSystem({
'/sub_entry_points': { '/sub_entry_points': {
'common': { 'common': {
'package.json': createPackageJson('common'), 'package.json': createPackageJson('common'),
@ -142,7 +138,7 @@ describe('findEntryPoints()', () => {
}, },
}, },
'/symlinked_folders': { '/symlinked_folders': {
'common': mockFs.symlink({path: '/sub_entry_points/common'}), 'common': new SymLink(_('/sub_entry_points/common')),
}, },
'/nested_node_modules': { '/nested_node_modules': {
'outer': { 'outer': {
@ -158,7 +154,6 @@ describe('findEntryPoints()', () => {
}, },
}); });
} }
function restoreRealFileSystem() { mockFs.restore(); }
}); });
function createPackageJson(packageName: string): string { function createPackageJson(packageName: string): string {

View File

@ -6,25 +6,20 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {readFileSync} from 'fs';
import * as mockFs from 'mock-fs';
import {AbsoluteFsPath} from '../../../src/ngtsc/path'; import {AbsoluteFsPath} from '../../../src/ngtsc/path';
import {NodeJSFileSystem} from '../../src/file_system/node_js_file_system'; import {FileSystem} from '../../src/file_system/file_system';
import {getEntryPointInfo} from '../../src/packages/entry_point'; import {getEntryPointInfo} from '../../src/packages/entry_point';
import {MockFileSystem} from '../helpers/mock_file_system';
import {MockLogger} from '../helpers/mock_logger'; import {MockLogger} from '../helpers/mock_logger';
const _ = AbsoluteFsPath.fromUnchecked; const _ = AbsoluteFsPath.fromUnchecked;
describe('getEntryPointInfo()', () => { describe('getEntryPointInfo()', () => {
beforeEach(createMockFileSystem);
afterEach(restoreRealFileSystem);
const SOME_PACKAGE = _('/some_package'); const SOME_PACKAGE = _('/some_package');
it('should return an object containing absolute paths to the formats of the specified entry-point', it('should return an object containing absolute paths to the formats of the specified entry-point',
() => { () => {
const fs = new NodeJSFileSystem(); const fs = createMockFileSystem();
const entryPoint = getEntryPointInfo( const entryPoint = getEntryPointInfo(
fs, new MockLogger(), SOME_PACKAGE, _('/some_package/valid_entry_point')); fs, new MockLogger(), SOME_PACKAGE, _('/some_package/valid_entry_point'));
expect(entryPoint).toEqual({ expect(entryPoint).toEqual({
@ -32,20 +27,20 @@ describe('getEntryPointInfo()', () => {
package: SOME_PACKAGE, package: SOME_PACKAGE,
path: _('/some_package/valid_entry_point'), path: _('/some_package/valid_entry_point'),
typings: _(`/some_package/valid_entry_point/valid_entry_point.d.ts`), typings: _(`/some_package/valid_entry_point/valid_entry_point.d.ts`),
packageJson: loadPackageJson('/some_package/valid_entry_point'), packageJson: loadPackageJson(fs, '/some_package/valid_entry_point'),
compiledByAngular: true, compiledByAngular: true,
}); });
}); });
it('should return null if there is no package.json at the entry-point path', () => { it('should return null if there is no package.json at the entry-point path', () => {
const fs = new NodeJSFileSystem(); const fs = createMockFileSystem();
const entryPoint = getEntryPointInfo( const entryPoint = getEntryPointInfo(
fs, new MockLogger(), SOME_PACKAGE, _('/some_package/missing_package_json')); fs, new MockLogger(), SOME_PACKAGE, _('/some_package/missing_package_json'));
expect(entryPoint).toBe(null); expect(entryPoint).toBe(null);
}); });
it('should return null if there is no typings or types field in the package.json', () => { it('should return null if there is no typings or types field in the package.json', () => {
const fs = new NodeJSFileSystem(); const fs = createMockFileSystem();
const entryPoint = const entryPoint =
getEntryPointInfo(fs, new MockLogger(), SOME_PACKAGE, _('/some_package/missing_typings')); getEntryPointInfo(fs, new MockLogger(), SOME_PACKAGE, _('/some_package/missing_typings'));
expect(entryPoint).toBe(null); expect(entryPoint).toBe(null);
@ -53,7 +48,7 @@ describe('getEntryPointInfo()', () => {
it('should return an object with `compiledByAngular` set to false if there is no metadata.json file next to the typing file', it('should return an object with `compiledByAngular` set to false if there is no metadata.json file next to the typing file',
() => { () => {
const fs = new NodeJSFileSystem(); const fs = createMockFileSystem();
const entryPoint = getEntryPointInfo( const entryPoint = getEntryPointInfo(
fs, new MockLogger(), SOME_PACKAGE, _('/some_package/missing_metadata')); fs, new MockLogger(), SOME_PACKAGE, _('/some_package/missing_metadata'));
expect(entryPoint).toEqual({ expect(entryPoint).toEqual({
@ -61,13 +56,13 @@ describe('getEntryPointInfo()', () => {
package: SOME_PACKAGE, package: SOME_PACKAGE,
path: _('/some_package/missing_metadata'), path: _('/some_package/missing_metadata'),
typings: _(`/some_package/missing_metadata/missing_metadata.d.ts`), typings: _(`/some_package/missing_metadata/missing_metadata.d.ts`),
packageJson: loadPackageJson('/some_package/missing_metadata'), packageJson: loadPackageJson(fs, '/some_package/missing_metadata'),
compiledByAngular: false, compiledByAngular: false,
}); });
}); });
it('should work if the typings field is named `types', () => { it('should work if the typings field is named `types', () => {
const fs = new NodeJSFileSystem(); const fs = createMockFileSystem();
const entryPoint = getEntryPointInfo( const entryPoint = getEntryPointInfo(
fs, new MockLogger(), SOME_PACKAGE, _('/some_package/types_rather_than_typings')); fs, new MockLogger(), SOME_PACKAGE, _('/some_package/types_rather_than_typings'));
expect(entryPoint).toEqual({ expect(entryPoint).toEqual({
@ -75,13 +70,13 @@ describe('getEntryPointInfo()', () => {
package: SOME_PACKAGE, package: SOME_PACKAGE,
path: _('/some_package/types_rather_than_typings'), path: _('/some_package/types_rather_than_typings'),
typings: _(`/some_package/types_rather_than_typings/types_rather_than_typings.d.ts`), typings: _(`/some_package/types_rather_than_typings/types_rather_than_typings.d.ts`),
packageJson: loadPackageJson('/some_package/types_rather_than_typings'), packageJson: loadPackageJson(fs, '/some_package/types_rather_than_typings'),
compiledByAngular: true, compiledByAngular: true,
}); });
}); });
it('should work with Angular Material style package.json', () => { it('should work with Angular Material style package.json', () => {
const fs = new NodeJSFileSystem(); const fs = createMockFileSystem();
const entryPoint = const entryPoint =
getEntryPointInfo(fs, new MockLogger(), SOME_PACKAGE, _('/some_package/material_style')); getEntryPointInfo(fs, new MockLogger(), SOME_PACKAGE, _('/some_package/material_style'));
expect(entryPoint).toEqual({ expect(entryPoint).toEqual({
@ -89,13 +84,13 @@ describe('getEntryPointInfo()', () => {
package: SOME_PACKAGE, package: SOME_PACKAGE,
path: _('/some_package/material_style'), path: _('/some_package/material_style'),
typings: _(`/some_package/material_style/material_style.d.ts`), typings: _(`/some_package/material_style/material_style.d.ts`),
packageJson: JSON.parse(readFileSync('/some_package/material_style/package.json', 'utf8')), packageJson: loadPackageJson(fs, '/some_package/material_style'),
compiledByAngular: true, compiledByAngular: true,
}); });
}); });
it('should return null if the package.json is not valid JSON', () => { it('should return null if the package.json is not valid JSON', () => {
const fs = new NodeJSFileSystem(); const fs = createMockFileSystem();
const entryPoint = getEntryPointInfo( const entryPoint = getEntryPointInfo(
fs, new MockLogger(), SOME_PACKAGE, _('/some_package/unexpected_symbols')); fs, new MockLogger(), SOME_PACKAGE, _('/some_package/unexpected_symbols'));
expect(entryPoint).toBe(null); expect(entryPoint).toBe(null);
@ -103,7 +98,7 @@ describe('getEntryPointInfo()', () => {
}); });
function createMockFileSystem() { function createMockFileSystem() {
mockFs({ return new MockFileSystem({
'/some_package': { '/some_package': {
'valid_entry_point': { 'valid_entry_point': {
'package.json': createPackageJson('valid_entry_point'), 'package.json': createPackageJson('valid_entry_point'),
@ -150,10 +145,6 @@ function createMockFileSystem() {
}); });
} }
function restoreRealFileSystem() {
mockFs.restore();
}
function createPackageJson( function createPackageJson(
packageName: string, {excludes}: {excludes?: string[]} = {}, packageName: string, {excludes}: {excludes?: string[]} = {},
typingsProp: string = 'typings'): string { typingsProp: string = 'typings'): string {
@ -172,6 +163,6 @@ function createPackageJson(
return JSON.stringify(packageJson); return JSON.stringify(packageJson);
} }
export function loadPackageJson(packagePath: string) { export function loadPackageJson(fs: FileSystem, packagePath: string) {
return JSON.parse(readFileSync(packagePath + '/package.json', 'utf8')); return JSON.parse(fs.readFile(_(packagePath + '/package.json')));
} }

View File

@ -11,21 +11,21 @@ import {AbsoluteFsPath} from '../../../src/ngtsc/path';
import {DecorationAnalyzer} from '../../src/analysis/decoration_analyzer'; import {DecorationAnalyzer} from '../../src/analysis/decoration_analyzer';
import {NgccReferencesRegistry} from '../../src/analysis/ngcc_references_registry'; import {NgccReferencesRegistry} from '../../src/analysis/ngcc_references_registry';
import {SwitchMarkerAnalyzer} from '../../src/analysis/switch_marker_analyzer'; import {SwitchMarkerAnalyzer} from '../../src/analysis/switch_marker_analyzer';
import {NodeJSFileSystem} from '../../src/file_system/node_js_file_system';
import {Esm2015ReflectionHost} from '../../src/host/esm2015_host'; import {Esm2015ReflectionHost} from '../../src/host/esm2015_host';
import {EsmRenderer} from '../../src/rendering/esm_renderer'; import {EsmRenderer} from '../../src/rendering/esm_renderer';
import {makeTestEntryPointBundle} from '../helpers/utils'; import {makeTestEntryPointBundle} from '../helpers/utils';
import {MockFileSystem} from '../helpers/mock_file_system';
import {MockLogger} from '../helpers/mock_logger'; import {MockLogger} from '../helpers/mock_logger';
const _ = AbsoluteFsPath.fromUnchecked; const _ = AbsoluteFsPath.fromUnchecked;
function setup(file: {name: string, contents: string}) { function setup(file: {name: AbsoluteFsPath, contents: string}) {
const fs = new MockFileSystem();
const logger = new MockLogger(); const logger = new MockLogger();
const bundle = makeTestEntryPointBundle('es2015', 'esm2015', false, [file]) !; const bundle = makeTestEntryPointBundle('es2015', 'esm2015', false, [file]) !;
const typeChecker = bundle.src.program.getTypeChecker(); const typeChecker = bundle.src.program.getTypeChecker();
const host = new Esm2015ReflectionHost(logger, false, typeChecker); const host = new Esm2015ReflectionHost(logger, false, typeChecker);
const referencesRegistry = new NgccReferencesRegistry(host); const referencesRegistry = new NgccReferencesRegistry(host);
const fs = new NodeJSFileSystem();
const decorationAnalyses = new DecorationAnalyzer( const decorationAnalyses = new DecorationAnalyzer(
fs, bundle.src.program, bundle.src.options, bundle.src.host, fs, bundle.src.program, bundle.src.options, bundle.src.host,
typeChecker, host, referencesRegistry, [_('/')], false) typeChecker, host, referencesRegistry, [_('/')], false)
@ -40,7 +40,7 @@ function setup(file: {name: string, contents: string}) {
} }
const PROGRAM = { const PROGRAM = {
name: '/some/file.js', name: _('/some/file.js'),
contents: ` contents: `
/* A copyright notice */ /* A copyright notice */
import 'some-side-effect'; import 'some-side-effect';
@ -76,7 +76,7 @@ function compileNgModuleFactory__POST_R3__(injector, options, moduleType) {
}; };
const PROGRAM_DECORATE_HELPER = { const PROGRAM_DECORATE_HELPER = {
name: '/some/file.js', name: _('/some/file.js'),
contents: ` contents: `
import * as tslib_1 from "tslib"; import * as tslib_1 from "tslib";
var D_1; var D_1;
@ -142,7 +142,7 @@ import * as i1 from '@angular/common';`);
{from: _('/some/a.js'), dtsFrom: _('/some/a.d.ts'), identifier: 'ComponentA1'}, {from: _('/some/a.js'), dtsFrom: _('/some/a.d.ts'), identifier: 'ComponentA1'},
{from: _('/some/a.js'), dtsFrom: _('/some/a.d.ts'), identifier: 'ComponentA2'}, {from: _('/some/a.js'), dtsFrom: _('/some/a.d.ts'), identifier: 'ComponentA2'},
{from: _('/some/foo/b.js'), dtsFrom: _('/some/foo/b.d.ts'), identifier: 'ComponentB'}, {from: _('/some/foo/b.js'), dtsFrom: _('/some/foo/b.d.ts'), identifier: 'ComponentB'},
{from: _(PROGRAM.name), dtsFrom: _(PROGRAM.name), identifier: 'TopLevelComponent'}, {from: PROGRAM.name, dtsFrom: PROGRAM.name, identifier: 'TopLevelComponent'},
]); ]);
expect(output.toString()).toContain(` expect(output.toString()).toContain(`
// Some other content // Some other content
@ -159,7 +159,7 @@ export {TopLevelComponent};`);
{from: _('/some/a.js'), alias: 'eComponentA1', identifier: 'ComponentA1'}, {from: _('/some/a.js'), alias: 'eComponentA1', identifier: 'ComponentA1'},
{from: _('/some/a.js'), alias: 'eComponentA2', identifier: 'ComponentA2'}, {from: _('/some/a.js'), alias: 'eComponentA2', identifier: 'ComponentA2'},
{from: _('/some/foo/b.js'), alias: 'eComponentB', identifier: 'ComponentB'}, {from: _('/some/foo/b.js'), alias: 'eComponentB', identifier: 'ComponentB'},
{from: _(PROGRAM.name), alias: 'eTopLevelComponent', identifier: 'TopLevelComponent'}, {from: PROGRAM.name, alias: 'eTopLevelComponent', identifier: 'TopLevelComponent'},
]); ]);
const outputString = output.toString(); const outputString = output.toString();
expect(outputString).not.toContain(`{eComponentA1 as ComponentA1}`); expect(outputString).not.toContain(`{eComponentA1 as ComponentA1}`);

View File

@ -11,24 +11,25 @@ import {AbsoluteFsPath} from '../../../src/ngtsc/path';
import {DecorationAnalyzer} from '../../src/analysis/decoration_analyzer'; import {DecorationAnalyzer} from '../../src/analysis/decoration_analyzer';
import {NgccReferencesRegistry} from '../../src/analysis/ngcc_references_registry'; import {NgccReferencesRegistry} from '../../src/analysis/ngcc_references_registry';
import {SwitchMarkerAnalyzer} from '../../src/analysis/switch_marker_analyzer'; import {SwitchMarkerAnalyzer} from '../../src/analysis/switch_marker_analyzer';
import {NodeJSFileSystem} from '../../src/file_system/node_js_file_system';
import {Esm5ReflectionHost} from '../../src/host/esm5_host'; import {Esm5ReflectionHost} from '../../src/host/esm5_host';
import {Esm5Renderer} from '../../src/rendering/esm5_renderer'; import {Esm5Renderer} from '../../src/rendering/esm5_renderer';
import {makeTestEntryPointBundle, getDeclaration} from '../helpers/utils'; import {makeTestEntryPointBundle, getDeclaration} from '../helpers/utils';
import {MockFileSystem} from '../helpers/mock_file_system';
import {MockLogger} from '../helpers/mock_logger'; import {MockLogger} from '../helpers/mock_logger';
const _ = AbsoluteFsPath.fromUnchecked; const _ = AbsoluteFsPath.fromUnchecked;
function setup(file: {name: string, contents: string}) { function setup(file: {name: AbsoluteFsPath, contents: string}) {
const fs = new MockFileSystem();
const logger = new MockLogger(); const logger = new MockLogger();
const bundle = makeTestEntryPointBundle('module', 'esm5', false, [file]); const bundle = makeTestEntryPointBundle('module', 'esm5', false, [file]);
const typeChecker = bundle.src.program.getTypeChecker(); const typeChecker = bundle.src.program.getTypeChecker();
const host = new Esm5ReflectionHost(logger, false, typeChecker); const host = new Esm5ReflectionHost(logger, false, typeChecker);
const referencesRegistry = new NgccReferencesRegistry(host); const referencesRegistry = new NgccReferencesRegistry(host);
const fs = new NodeJSFileSystem(); const decorationAnalyses =
const decorationAnalyses = new DecorationAnalyzer( new DecorationAnalyzer(
fs, bundle.src.program, bundle.src.options, bundle.src.host, fs, bundle.src.program, bundle.src.options, bundle.src.host, typeChecker, host,
typeChecker, host, referencesRegistry, [_('/')], false) referencesRegistry, [AbsoluteFsPath.fromUnchecked('/')], false)
.analyzeProgram(); .analyzeProgram();
const switchMarkerAnalyses = new SwitchMarkerAnalyzer(host).analyzeProgram(bundle.src.program); const switchMarkerAnalyses = new SwitchMarkerAnalyzer(host).analyzeProgram(bundle.src.program);
const renderer = new Esm5Renderer(fs, logger, host, false, bundle); const renderer = new Esm5Renderer(fs, logger, host, false, bundle);
@ -40,7 +41,7 @@ function setup(file: {name: string, contents: string}) {
} }
const PROGRAM = { const PROGRAM = {
name: '/some/file.js', name: _('/some/file.js'),
contents: ` contents: `
/* A copyright notice */ /* A copyright notice */
import 'some-side-effect'; import 'some-side-effect';
@ -100,7 +101,7 @@ export {A, B, C, NoIife, BadIife};`
}; };
const PROGRAM_DECORATE_HELPER = { const PROGRAM_DECORATE_HELPER = {
name: '/some/file.js', name: _('/some/file.js'),
contents: ` contents: `
import * as tslib_1 from "tslib"; import * as tslib_1 from "tslib";
/* A copyright notice */ /* A copyright notice */
@ -179,7 +180,7 @@ import * as i1 from '@angular/common';`);
{from: _('/some/a.js'), dtsFrom: _('/some/a.d.ts'), identifier: 'ComponentA1'}, {from: _('/some/a.js'), dtsFrom: _('/some/a.d.ts'), identifier: 'ComponentA1'},
{from: _('/some/a.js'), dtsFrom: _('/some/a.d.ts'), identifier: 'ComponentA2'}, {from: _('/some/a.js'), dtsFrom: _('/some/a.d.ts'), identifier: 'ComponentA2'},
{from: _('/some/foo/b.js'), dtsFrom: _('/some/foo/b.d.ts'), identifier: 'ComponentB'}, {from: _('/some/foo/b.js'), dtsFrom: _('/some/foo/b.d.ts'), identifier: 'ComponentB'},
{from: _(PROGRAM.name), dtsFrom: _(PROGRAM.name), identifier: 'TopLevelComponent'}, {from: PROGRAM.name, dtsFrom: PROGRAM.name, identifier: 'TopLevelComponent'},
]); ]);
expect(output.toString()).toContain(` expect(output.toString()).toContain(`
export {A, B, C, NoIife, BadIife}; export {A, B, C, NoIife, BadIife};
@ -193,10 +194,10 @@ export {TopLevelComponent};`);
const {renderer} = setup(PROGRAM); const {renderer} = setup(PROGRAM);
const output = new MagicString(PROGRAM.contents); const output = new MagicString(PROGRAM.contents);
renderer.addExports(output, _(PROGRAM.name.replace(/\.js$/, '')), [ renderer.addExports(output, _(PROGRAM.name.replace(/\.js$/, '')), [
{from: _('/some/a.js'), alias: 'eComponentA1', identifier: 'ComponentA1'}, {from: _('/some/a.js'), alias: _('eComponentA1'), identifier: 'ComponentA1'},
{from: _('/some/a.js'), alias: 'eComponentA2', identifier: 'ComponentA2'}, {from: _('/some/a.js'), alias: _('eComponentA2'), identifier: 'ComponentA2'},
{from: _('/some/foo/b.js'), alias: 'eComponentB', identifier: 'ComponentB'}, {from: _('/some/foo/b.js'), alias: _('eComponentB'), identifier: 'ComponentB'},
{from: _(PROGRAM.name), alias: 'eTopLevelComponent', identifier: 'TopLevelComponent'}, {from: PROGRAM.name, alias: 'eTopLevelComponent', identifier: 'TopLevelComponent'},
]); ]);
const outputString = output.toString(); const outputString = output.toString();
expect(outputString).not.toContain(`{eComponentA1 as ComponentA1}`); expect(outputString).not.toContain(`{eComponentA1 as ComponentA1}`);
@ -285,14 +286,14 @@ SOME DEFINITION TEXT
const noIifeDeclaration = const noIifeDeclaration =
getDeclaration(program, sourceFile.fileName, 'NoIife', ts.isFunctionDeclaration); getDeclaration(program, sourceFile.fileName, 'NoIife', ts.isFunctionDeclaration);
const mockNoIifeClass: any = {declaration: noIifeDeclaration, name: 'NoIife'}; const mockNoIifeClass: any = {declaration: noIifeDeclaration, name: _('NoIife')};
expect(() => renderer.addDefinitions(output, mockNoIifeClass, 'SOME DEFINITION TEXT')) expect(() => renderer.addDefinitions(output, mockNoIifeClass, 'SOME DEFINITION TEXT'))
.toThrowError( .toThrowError(
'Compiled class declaration is not inside an IIFE: NoIife in /some/file.js'); 'Compiled class declaration is not inside an IIFE: NoIife in /some/file.js');
const badIifeDeclaration = const badIifeDeclaration =
getDeclaration(program, sourceFile.fileName, 'BadIife', ts.isVariableDeclaration); getDeclaration(program, sourceFile.fileName, 'BadIife', ts.isVariableDeclaration);
const mockBadIifeClass: any = {declaration: badIifeDeclaration, name: 'BadIife'}; const mockBadIifeClass: any = {declaration: badIifeDeclaration, name: _('BadIife')};
expect(() => renderer.addDefinitions(output, mockBadIifeClass, 'SOME DEFINITION TEXT')) expect(() => renderer.addDefinitions(output, mockBadIifeClass, 'SOME DEFINITION TEXT'))
.toThrowError( .toThrowError(
'Compiled class wrapper IIFE does not have a return statement: BadIife in /some/file.js'); 'Compiled class wrapper IIFE does not have a return statement: BadIife in /some/file.js');

View File

@ -5,7 +5,6 @@
* Use of this source code is governed by an MIT-style license that can be * Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import * as fs from 'fs';
import MagicString from 'magic-string'; import MagicString from 'magic-string';
import * as ts from 'typescript'; import * as ts from 'typescript';
import {fromObject, generateMapFileComment} from 'convert-source-map'; import {fromObject, generateMapFileComment} from 'convert-source-map';
@ -18,11 +17,11 @@ import {SwitchMarkerAnalyzer} from '../../src/analysis/switch_marker_analyzer';
import {Esm2015ReflectionHost} from '../../src/host/esm2015_host'; import {Esm2015ReflectionHost} from '../../src/host/esm2015_host';
import {RedundantDecoratorMap, Renderer} from '../../src/rendering/renderer'; import {RedundantDecoratorMap, Renderer} from '../../src/rendering/renderer';
import {EntryPointBundle} from '../../src/packages/entry_point_bundle'; import {EntryPointBundle} from '../../src/packages/entry_point_bundle';
import {makeTestEntryPointBundle} from '../helpers/utils'; import {makeTestEntryPointBundle, createFileSystemFromProgramFiles} from '../helpers/utils';
import {Logger} from '../../src/logging/logger'; import {Logger} from '../../src/logging/logger';
import {MockFileSystem} from '../helpers/mock_file_system';
import {MockLogger} from '../helpers/mock_logger'; import {MockLogger} from '../helpers/mock_logger';
import {FileSystem} from '../../src/file_system/file_system'; import {FileSystem} from '../../src/file_system/file_system';
import {NodeJSFileSystem} from '../../src/file_system/node_js_file_system';
const _ = AbsoluteFsPath.fromUnchecked; const _ = AbsoluteFsPath.fromUnchecked;
@ -58,14 +57,15 @@ class TestRenderer extends Renderer {
function createTestRenderer( function createTestRenderer(
packageName: string, files: {name: string, contents: string}[], packageName: string, files: {name: string, contents: string}[],
dtsFiles?: {name: string, contents: string}[]) { dtsFiles?: {name: string, contents: string}[],
mappingFiles?: {name: string, contents: string}[]) {
const logger = new MockLogger(); const logger = new MockLogger();
const fs = new MockFileSystem(createFileSystemFromProgramFiles(files, dtsFiles, mappingFiles));
const isCore = packageName === '@angular/core'; const isCore = packageName === '@angular/core';
const bundle = makeTestEntryPointBundle('es2015', 'esm2015', isCore, files, dtsFiles); const bundle = makeTestEntryPointBundle('es2015', 'esm2015', isCore, files, dtsFiles);
const typeChecker = bundle.src.program.getTypeChecker(); const typeChecker = bundle.src.program.getTypeChecker();
const host = new Esm2015ReflectionHost(logger, isCore, typeChecker, bundle.dts); const host = new Esm2015ReflectionHost(logger, isCore, typeChecker, bundle.dts);
const referencesRegistry = new NgccReferencesRegistry(host); const referencesRegistry = new NgccReferencesRegistry(host);
const fs = new NodeJSFileSystem();
const decorationAnalyses = new DecorationAnalyzer( const decorationAnalyses = new DecorationAnalyzer(
fs, bundle.src.program, bundle.src.options, bundle.src.host, fs, bundle.src.program, bundle.src.options, bundle.src.host,
typeChecker, host, referencesRegistry, bundle.rootDirs, isCore) typeChecker, host, referencesRegistry, bundle.rootDirs, isCore)
@ -200,8 +200,8 @@ describe('Renderer', () => {
const addDefinitionsSpy = renderer.addDefinitions as jasmine.Spy; const addDefinitionsSpy = renderer.addDefinitions as jasmine.Spy;
expect(addDefinitionsSpy.calls.first().args[0].toString()).toEqual(RENDERED_CONTENTS); expect(addDefinitionsSpy.calls.first().args[0].toString()).toEqual(RENDERED_CONTENTS);
expect(addDefinitionsSpy.calls.first().args[1]).toEqual(jasmine.objectContaining({ expect(addDefinitionsSpy.calls.first().args[1]).toEqual(jasmine.objectContaining({
name: 'A', name: _('A'),
decorators: [jasmine.objectContaining({name: 'Directive'})], decorators: [jasmine.objectContaining({name: _('Directive')})]
})); }));
expect(addDefinitionsSpy.calls.first().args[2]) expect(addDefinitionsSpy.calls.first().args[2])
.toEqual( .toEqual(
@ -259,15 +259,15 @@ describe('Renderer', () => {
it('should merge any external source map from the original file and write the output to an external source map', it('should merge any external source map from the original file and write the output to an external source map',
() => { () => {
// Mock out reading the map file from disk const sourceFiles = [{
spyOn(fs, 'readFileSync').and.returnValue(INPUT_PROGRAM_MAP.toJSON());
const {decorationAnalyses, renderer, switchMarkerAnalyses, privateDeclarationsAnalyses,
moduleWithProvidersAnalyses} =
createTestRenderer(
'test-package', [{
...INPUT_PROGRAM, ...INPUT_PROGRAM,
contents: INPUT_PROGRAM.contents + '\n//# sourceMappingURL=file.js.map' contents: INPUT_PROGRAM.contents + '\n//# sourceMappingURL=file.js.map'
}]); }];
const mappingFiles =
[{name: INPUT_PROGRAM.name + '.map', contents: INPUT_PROGRAM_MAP.toJSON()}];
const {decorationAnalyses, renderer, switchMarkerAnalyses, privateDeclarationsAnalyses,
moduleWithProvidersAnalyses} =
createTestRenderer('test-package', sourceFiles, undefined, mappingFiles);
const result = renderer.renderProgram( const result = renderer.renderProgram(
decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses, decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses,
moduleWithProvidersAnalyses); moduleWithProvidersAnalyses);
@ -275,7 +275,7 @@ describe('Renderer', () => {
expect(result[0].contents) expect(result[0].contents)
.toEqual(RENDERED_CONTENTS + '\n' + generateMapFileComment('file.js.map')); .toEqual(RENDERED_CONTENTS + '\n' + generateMapFileComment('file.js.map'));
expect(result[1].path).toEqual('/src/file.js.map'); expect(result[1].path).toEqual('/src/file.js.map');
expect(result[1].contents).toEqual(MERGED_OUTPUT_PROGRAM_MAP.toJSON()); expect(JSON.parse(result[1].contents)).toEqual(MERGED_OUTPUT_PROGRAM_MAP.toObject());
}); });
}); });

View File

@ -5,20 +5,16 @@
* Use of this source code is governed by an MIT-style license that can be * Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {existsSync, readFileSync} from 'fs';
import * as mockFs from 'mock-fs';
import {AbsoluteFsPath} from '../../../src/ngtsc/path'; import {AbsoluteFsPath} from '../../../src/ngtsc/path';
import {NodeJSFileSystem} from '../../src/file_system/node_js_file_system';
import {EntryPoint} from '../../src/packages/entry_point'; import {EntryPoint} from '../../src/packages/entry_point';
import {EntryPointBundle} from '../../src/packages/entry_point_bundle'; import {EntryPointBundle} from '../../src/packages/entry_point_bundle';
import {InPlaceFileWriter} from '../../src/writing/in_place_file_writer'; import {InPlaceFileWriter} from '../../src/writing/in_place_file_writer';
import {MockFileSystem} from '../helpers/mock_file_system';
const _ = AbsoluteFsPath.fromUnchecked; const _ = AbsoluteFsPath.fromUnchecked;
function createMockFileSystem() { function createMockFileSystem() {
mockFs({ return new MockFileSystem({
'/package/path': { '/package/path': {
'top-level.js': 'ORIGINAL TOP LEVEL', 'top-level.js': 'ORIGINAL TOP LEVEL',
'folder-1': { 'folder-1': {
@ -34,16 +30,9 @@ function createMockFileSystem() {
}); });
} }
function restoreRealFileSystem() {
mockFs.restore();
}
describe('InPlaceFileWriter', () => { describe('InPlaceFileWriter', () => {
beforeEach(createMockFileSystem);
afterEach(restoreRealFileSystem);
it('should write all the FileInfo to the disk', () => { it('should write all the FileInfo to the disk', () => {
const fs = new NodeJSFileSystem(); const fs = createMockFileSystem();
const fileWriter = new InPlaceFileWriter(fs); const fileWriter = new InPlaceFileWriter(fs);
fileWriter.writeBundle({} as EntryPoint, {} as EntryPointBundle, [ fileWriter.writeBundle({} as EntryPoint, {} as EntryPointBundle, [
{path: _('/package/path/top-level.js'), contents: 'MODIFIED TOP LEVEL'}, {path: _('/package/path/top-level.js'), contents: 'MODIFIED TOP LEVEL'},
@ -51,16 +40,16 @@ describe('InPlaceFileWriter', () => {
{path: _('/package/path/folder-2/file-4.js'), contents: 'MODIFIED FILE 4'}, {path: _('/package/path/folder-2/file-4.js'), contents: 'MODIFIED FILE 4'},
{path: _('/package/path/folder-3/file-5.js'), contents: 'NEW FILE 5'}, {path: _('/package/path/folder-3/file-5.js'), contents: 'NEW FILE 5'},
]); ]);
expect(readFileSync('/package/path/top-level.js', 'utf8')).toEqual('MODIFIED TOP LEVEL'); expect(fs.readFile(_('/package/path/top-level.js'))).toEqual('MODIFIED TOP LEVEL');
expect(readFileSync('/package/path/folder-1/file-1.js', 'utf8')).toEqual('MODIFIED FILE 1'); expect(fs.readFile(_('/package/path/folder-1/file-1.js'))).toEqual('MODIFIED FILE 1');
expect(readFileSync('/package/path/folder-1/file-2.js', 'utf8')).toEqual('ORIGINAL FILE 2'); expect(fs.readFile(_('/package/path/folder-1/file-2.js'))).toEqual('ORIGINAL FILE 2');
expect(readFileSync('/package/path/folder-2/file-3.js', 'utf8')).toEqual('ORIGINAL FILE 3'); expect(fs.readFile(_('/package/path/folder-2/file-3.js'))).toEqual('ORIGINAL FILE 3');
expect(readFileSync('/package/path/folder-2/file-4.js', 'utf8')).toEqual('MODIFIED FILE 4'); expect(fs.readFile(_('/package/path/folder-2/file-4.js'))).toEqual('MODIFIED FILE 4');
expect(readFileSync('/package/path/folder-3/file-5.js', 'utf8')).toEqual('NEW FILE 5'); expect(fs.readFile(_('/package/path/folder-3/file-5.js'))).toEqual('NEW FILE 5');
}); });
it('should create backups of all files that previously existed', () => { it('should create backups of all files that previously existed', () => {
const fs = new NodeJSFileSystem(); const fs = createMockFileSystem();
const fileWriter = new InPlaceFileWriter(fs); const fileWriter = new InPlaceFileWriter(fs);
fileWriter.writeBundle({} as EntryPoint, {} as EntryPointBundle, [ fileWriter.writeBundle({} as EntryPoint, {} as EntryPointBundle, [
{path: _('/package/path/top-level.js'), contents: 'MODIFIED TOP LEVEL'}, {path: _('/package/path/top-level.js'), contents: 'MODIFIED TOP LEVEL'},
@ -68,19 +57,19 @@ describe('InPlaceFileWriter', () => {
{path: _('/package/path/folder-2/file-4.js'), contents: 'MODIFIED FILE 4'}, {path: _('/package/path/folder-2/file-4.js'), contents: 'MODIFIED FILE 4'},
{path: _('/package/path/folder-3/file-5.js'), contents: 'NEW FILE 5'}, {path: _('/package/path/folder-3/file-5.js'), contents: 'NEW FILE 5'},
]); ]);
expect(readFileSync('/package/path/top-level.js.__ivy_ngcc_bak', 'utf8')) expect(fs.readFile(_('/package/path/top-level.js.__ivy_ngcc_bak')))
.toEqual('ORIGINAL TOP LEVEL'); .toEqual('ORIGINAL TOP LEVEL');
expect(readFileSync('/package/path/folder-1/file-1.js.__ivy_ngcc_bak', 'utf8')) expect(fs.readFile(_('/package/path/folder-1/file-1.js.__ivy_ngcc_bak')))
.toEqual('ORIGINAL FILE 1'); .toEqual('ORIGINAL FILE 1');
expect(existsSync('/package/path/folder-1/file-2.js.__ivy_ngcc_bak')).toBe(false); expect(fs.exists(_('/package/path/folder-1/file-2.js.__ivy_ngcc_bak'))).toBe(false);
expect(existsSync('/package/path/folder-2/file-3.js.__ivy_ngcc_bak')).toBe(false); expect(fs.exists(_('/package/path/folder-2/file-3.js.__ivy_ngcc_bak'))).toBe(false);
expect(readFileSync('/package/path/folder-2/file-4.js.__ivy_ngcc_bak', 'utf8')) expect(fs.readFile(_('/package/path/folder-2/file-4.js.__ivy_ngcc_bak')))
.toEqual('ORIGINAL FILE 4'); .toEqual('ORIGINAL FILE 4');
expect(existsSync('/package/path/folder-3/file-5.js.__ivy_ngcc_bak')).toBe(false); expect(fs.exists(_('/package/path/folder-3/file-5.js.__ivy_ngcc_bak'))).toBe(false);
}); });
it('should error if the backup file already exists', () => { it('should error if the backup file already exists', () => {
const fs = new NodeJSFileSystem(); const fs = createMockFileSystem();
const fileWriter = new InPlaceFileWriter(fs); const fileWriter = new InPlaceFileWriter(fs);
expect( expect(
() => fileWriter.writeBundle( () => fileWriter.writeBundle(

View File

@ -5,24 +5,20 @@
* Use of this source code is governed by an MIT-style license that can be * Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {existsSync, readFileSync} from 'fs';
import * as mockFs from 'mock-fs';
import {AbsoluteFsPath} from '../../../src/ngtsc/path'; import {AbsoluteFsPath} from '../../../src/ngtsc/path';
import {FileSystem} from '../../src/file_system/file_system'; import {FileSystem} from '../../src/file_system/file_system';
import {NodeJSFileSystem} from '../../src/file_system/node_js_file_system';
import {EntryPoint, EntryPointFormat, EntryPointJsonProperty, getEntryPointInfo} from '../../src/packages/entry_point'; import {EntryPoint, EntryPointFormat, EntryPointJsonProperty, getEntryPointInfo} from '../../src/packages/entry_point';
import {EntryPointBundle, makeEntryPointBundle} from '../../src/packages/entry_point_bundle'; import {EntryPointBundle, makeEntryPointBundle} from '../../src/packages/entry_point_bundle';
import {FileWriter} from '../../src/writing/file_writer'; import {FileWriter} from '../../src/writing/file_writer';
import {NewEntryPointFileWriter} from '../../src/writing/new_entry_point_file_writer'; import {NewEntryPointFileWriter} from '../../src/writing/new_entry_point_file_writer';
import {MockFileSystem} from '../helpers/mock_file_system';
import {MockLogger} from '../helpers/mock_logger'; import {MockLogger} from '../helpers/mock_logger';
import {loadPackageJson} from '../packages/entry_point_spec'; import {loadPackageJson} from '../packages/entry_point_spec';
const _ = AbsoluteFsPath.from; const _ = AbsoluteFsPath.from;
function createMockFileSystem() { function createMockFileSystem() {
mockFs({ return new MockFileSystem({
'/node_modules/test': { '/node_modules/test': {
'package.json': 'package.json':
'{"module": "./esm5.js", "es2015": "./es2015/index.js", "typings": "./index.d.ts"}', '{"module": "./esm5.js", "es2015": "./es2015/index.js", "typings": "./index.d.ts"}',
@ -75,14 +71,7 @@ function createMockFileSystem() {
}); });
} }
function restoreRealFileSystem() {
mockFs.restore();
}
describe('NewEntryPointFileWriter', () => { describe('NewEntryPointFileWriter', () => {
beforeEach(createMockFileSystem);
afterEach(restoreRealFileSystem);
let fs: FileSystem; let fs: FileSystem;
let fileWriter: FileWriter; let fileWriter: FileWriter;
let entryPoint: EntryPoint; let entryPoint: EntryPoint;
@ -91,7 +80,7 @@ describe('NewEntryPointFileWriter', () => {
describe('writeBundle() [primary entry-point]', () => { describe('writeBundle() [primary entry-point]', () => {
beforeEach(() => { beforeEach(() => {
fs = new NodeJSFileSystem(); fs = createMockFileSystem();
fileWriter = new NewEntryPointFileWriter(fs); fileWriter = new NewEntryPointFileWriter(fs);
entryPoint = getEntryPointInfo( entryPoint = getEntryPointInfo(
fs, new MockLogger(), _('/node_modules/test'), _('/node_modules/test')) !; fs, new MockLogger(), _('/node_modules/test'), _('/node_modules/test')) !;
@ -107,14 +96,12 @@ describe('NewEntryPointFileWriter', () => {
}, },
{path: _('/node_modules/test/esm5.js.map'), contents: 'MODIFIED MAPPING DATA'}, {path: _('/node_modules/test/esm5.js.map'), contents: 'MODIFIED MAPPING DATA'},
]); ]);
expect(readFileSync('/node_modules/test/__ivy_ngcc__/esm5.js', 'utf8')) expect(fs.readFile(_('/node_modules/test/__ivy_ngcc__/esm5.js')))
.toEqual('export function FooTop() {} // MODIFIED'); .toEqual('export function FooTop() {} // MODIFIED');
expect(readFileSync('/node_modules/test/esm5.js', 'utf8')) expect(fs.readFile(_('/node_modules/test/esm5.js'))).toEqual('export function FooTop() {}');
.toEqual('export function FooTop() {}'); expect(fs.readFile(_('/node_modules/test/__ivy_ngcc__/esm5.js.map')))
expect(readFileSync('/node_modules/test/__ivy_ngcc__/esm5.js.map', 'utf8'))
.toEqual('MODIFIED MAPPING DATA'); .toEqual('MODIFIED MAPPING DATA');
expect(readFileSync('/node_modules/test/esm5.js.map', 'utf8')) expect(fs.readFile(_('/node_modules/test/esm5.js.map'))).toEqual('ORIGINAL MAPPING DATA');
.toEqual('ORIGINAL MAPPING DATA');
}); });
it('should also copy unmodified files in the program', () => { it('should also copy unmodified files in the program', () => {
@ -124,13 +111,12 @@ describe('NewEntryPointFileWriter', () => {
contents: 'export class FooTop {} // MODIFIED' contents: 'export class FooTop {} // MODIFIED'
}, },
]); ]);
expect(readFileSync('/node_modules/test/__ivy_ngcc__/es2015/foo.js', 'utf8')) expect(fs.readFile(_('/node_modules/test/__ivy_ngcc__/es2015/foo.js')))
.toEqual('export class FooTop {} // MODIFIED'); .toEqual('export class FooTop {} // MODIFIED');
expect(readFileSync('/node_modules/test/es2015/foo.js', 'utf8')) expect(fs.readFile(_('/node_modules/test/es2015/foo.js'))).toEqual('export class FooTop {}');
.toEqual('export class FooTop {}'); expect(fs.readFile(_('/node_modules/test/__ivy_ngcc__/es2015/index.js')))
expect(readFileSync('/node_modules/test/__ivy_ngcc__/es2015/index.js', 'utf8'))
.toEqual('export {FooTop} from "./foo";'); .toEqual('export {FooTop} from "./foo";');
expect(readFileSync('/node_modules/test/es2015/index.js', 'utf8')) expect(fs.readFile(_('/node_modules/test/es2015/index.js')))
.toEqual('export {FooTop} from "./foo";'); .toEqual('export {FooTop} from "./foo";');
}); });
@ -141,7 +127,7 @@ describe('NewEntryPointFileWriter', () => {
contents: 'export function FooTop() {} // MODIFIED' contents: 'export function FooTop() {} // MODIFIED'
}, },
]); ]);
expect(loadPackageJson('/node_modules/test')).toEqual(jasmine.objectContaining({ expect(loadPackageJson(fs, '/node_modules/test')).toEqual(jasmine.objectContaining({
module_ivy_ngcc: '__ivy_ngcc__/esm5.js', module_ivy_ngcc: '__ivy_ngcc__/esm5.js',
})); }));
@ -151,7 +137,7 @@ describe('NewEntryPointFileWriter', () => {
contents: 'export class FooTop {} // MODIFIED' contents: 'export class FooTop {} // MODIFIED'
}, },
]); ]);
expect(loadPackageJson('/node_modules/test')).toEqual(jasmine.objectContaining({ expect(loadPackageJson(fs, '/node_modules/test')).toEqual(jasmine.objectContaining({
module_ivy_ngcc: '__ivy_ngcc__/esm5.js', module_ivy_ngcc: '__ivy_ngcc__/esm5.js',
es2015_ivy_ngcc: '__ivy_ngcc__/es2015/index.js', es2015_ivy_ngcc: '__ivy_ngcc__/es2015/index.js',
})); }));
@ -161,27 +147,26 @@ describe('NewEntryPointFileWriter', () => {
fileWriter.writeBundle(entryPoint, esm2015bundle, [ fileWriter.writeBundle(entryPoint, esm2015bundle, [
{ {
path: _('/node_modules/test/index.d.ts'), path: _('/node_modules/test/index.d.ts'),
contents: 'export declare class FooTop {} // MODIFIED', contents: 'export declare class FooTop {} // MODIFIED'
}, },
{path: _('/node_modules/test/index.d.ts.map'), contents: 'MODIFIED MAPPING DATA'}, {path: _('/node_modules/test/index.d.ts.map'), contents: 'MODIFIED MAPPING DATA'},
]); ]);
expect(readFileSync('/node_modules/test/index.d.ts', 'utf8')) expect(fs.readFile(_('/node_modules/test/index.d.ts')))
.toEqual('export declare class FooTop {} // MODIFIED'); .toEqual('export declare class FooTop {} // MODIFIED');
expect(readFileSync('/node_modules/test/index.d.ts.__ivy_ngcc_bak', 'utf8')) expect(fs.readFile(_('/node_modules/test/index.d.ts.__ivy_ngcc_bak')))
.toEqual('export declare class FooTop {}'); .toEqual('export declare class FooTop {}');
expect(existsSync('/node_modules/test/__ivy_ngcc__/index.d.ts')).toBe(false); expect(fs.exists(_('/node_modules/test/__ivy_ngcc__/index.d.ts'))).toBe(false);
expect(readFileSync('/node_modules/test/index.d.ts.map', 'utf8')) expect(fs.readFile(_('/node_modules/test/index.d.ts.map'))).toEqual('MODIFIED MAPPING DATA');
.toEqual('MODIFIED MAPPING DATA'); expect(fs.readFile(_('/node_modules/test/index.d.ts.map.__ivy_ngcc_bak')))
expect(readFileSync('/node_modules/test/index.d.ts.map.__ivy_ngcc_bak', 'utf8'))
.toEqual('ORIGINAL MAPPING DATA'); .toEqual('ORIGINAL MAPPING DATA');
expect(existsSync('/node_modules/test/__ivy_ngcc__/index.d.ts.map')).toBe(false); expect(fs.exists(_('/node_modules/test/__ivy_ngcc__/index.d.ts.map'))).toBe(false);
}); });
}); });
describe('writeBundle() [secondary entry-point]', () => { describe('writeBundle() [secondary entry-point]', () => {
beforeEach(() => { beforeEach(() => {
fs = new NodeJSFileSystem(); fs = createMockFileSystem();
fileWriter = new NewEntryPointFileWriter(fs); fileWriter = new NewEntryPointFileWriter(fs);
entryPoint = getEntryPointInfo( entryPoint = getEntryPointInfo(
fs, new MockLogger(), _('/node_modules/test'), _('/node_modules/test/a')) !; fs, new MockLogger(), _('/node_modules/test'), _('/node_modules/test/a')) !;
@ -196,10 +181,9 @@ describe('NewEntryPointFileWriter', () => {
contents: 'export function FooA() {} // MODIFIED' contents: 'export function FooA() {} // MODIFIED'
}, },
]); ]);
expect(readFileSync('/node_modules/test/__ivy_ngcc__/a/esm5.js', 'utf8')) expect(fs.readFile(_('/node_modules/test/__ivy_ngcc__/a/esm5.js')))
.toEqual('export function FooA() {} // MODIFIED'); .toEqual('export function FooA() {} // MODIFIED');
expect(readFileSync('/node_modules/test/a/esm5.js', 'utf8')) expect(fs.readFile(_('/node_modules/test/a/esm5.js'))).toEqual('export function FooA() {}');
.toEqual('export function FooA() {}');
}); });
it('should also copy unmodified files in the program', () => { it('should also copy unmodified files in the program', () => {
@ -209,13 +193,12 @@ describe('NewEntryPointFileWriter', () => {
contents: 'export class FooA {} // MODIFIED' contents: 'export class FooA {} // MODIFIED'
}, },
]); ]);
expect(readFileSync('/node_modules/test/__ivy_ngcc__/a/es2015/foo.js', 'utf8')) expect(fs.readFile(_('/node_modules/test/__ivy_ngcc__/a/es2015/foo.js')))
.toEqual('export class FooA {} // MODIFIED'); .toEqual('export class FooA {} // MODIFIED');
expect(readFileSync('/node_modules/test/a/es2015/foo.js', 'utf8')) expect(fs.readFile(_('/node_modules/test/a/es2015/foo.js'))).toEqual('export class FooA {}');
.toEqual('export class FooA {}'); expect(fs.readFile(_('/node_modules/test/__ivy_ngcc__/a/es2015/index.js')))
expect(readFileSync('/node_modules/test/__ivy_ngcc__/a/es2015/index.js', 'utf8'))
.toEqual('export {FooA} from "./foo";'); .toEqual('export {FooA} from "./foo";');
expect(readFileSync('/node_modules/test/a/es2015/index.js', 'utf8')) expect(fs.readFile(_('/node_modules/test/a/es2015/index.js')))
.toEqual('export {FooA} from "./foo";'); .toEqual('export {FooA} from "./foo";');
}); });
@ -226,7 +209,7 @@ describe('NewEntryPointFileWriter', () => {
contents: 'export function FooA() {} // MODIFIED' contents: 'export function FooA() {} // MODIFIED'
}, },
]); ]);
expect(loadPackageJson('/node_modules/test/a')).toEqual(jasmine.objectContaining({ expect(loadPackageJson(fs, '/node_modules/test/a')).toEqual(jasmine.objectContaining({
module_ivy_ngcc: '../__ivy_ngcc__/a/esm5.js', module_ivy_ngcc: '../__ivy_ngcc__/a/esm5.js',
})); }));
@ -236,7 +219,7 @@ describe('NewEntryPointFileWriter', () => {
contents: 'export class FooA {} // MODIFIED' contents: 'export class FooA {} // MODIFIED'
}, },
]); ]);
expect(loadPackageJson('/node_modules/test/a')).toEqual(jasmine.objectContaining({ expect(loadPackageJson(fs, '/node_modules/test/a')).toEqual(jasmine.objectContaining({
module_ivy_ngcc: '../__ivy_ngcc__/a/esm5.js', module_ivy_ngcc: '../__ivy_ngcc__/a/esm5.js',
es2015_ivy_ngcc: '../__ivy_ngcc__/a/es2015/index.js', es2015_ivy_ngcc: '../__ivy_ngcc__/a/es2015/index.js',
})); }));
@ -249,17 +232,17 @@ describe('NewEntryPointFileWriter', () => {
contents: 'export declare class FooA {} // MODIFIED' contents: 'export declare class FooA {} // MODIFIED'
}, },
]); ]);
expect(readFileSync('/node_modules/test/a/index.d.ts', 'utf8')) expect(fs.readFile(_('/node_modules/test/a/index.d.ts')))
.toEqual('export declare class FooA {} // MODIFIED'); .toEqual('export declare class FooA {} // MODIFIED');
expect(readFileSync('/node_modules/test/a/index.d.ts.__ivy_ngcc_bak', 'utf8')) expect(fs.readFile(_('/node_modules/test/a/index.d.ts.__ivy_ngcc_bak')))
.toEqual('export declare class FooA {}'); .toEqual('export declare class FooA {}');
expect(existsSync('/node_modules/test/__ivy_ngcc__/a/index.d.ts')).toBe(false); expect(fs.exists(_('/node_modules/test/__ivy_ngcc__/a/index.d.ts'))).toBe(false);
}); });
}); });
describe('writeBundle() [entry-point (with files placed outside entry-point folder)]', () => { describe('writeBundle() [entry-point (with files placed outside entry-point folder)]', () => {
beforeEach(() => { beforeEach(() => {
fs = new NodeJSFileSystem(); fs = createMockFileSystem();
fileWriter = new NewEntryPointFileWriter(fs); fileWriter = new NewEntryPointFileWriter(fs);
entryPoint = getEntryPointInfo( entryPoint = getEntryPointInfo(
fs, new MockLogger(), _('/node_modules/test'), _('/node_modules/test/b')) !; fs, new MockLogger(), _('/node_modules/test'), _('/node_modules/test/b')) !;
@ -274,10 +257,9 @@ describe('NewEntryPointFileWriter', () => {
contents: 'export function FooB() {} // MODIFIED' contents: 'export function FooB() {} // MODIFIED'
}, },
]); ]);
expect(readFileSync('/node_modules/test/__ivy_ngcc__/lib/esm5.js', 'utf8')) expect(fs.readFile(_('/node_modules/test/__ivy_ngcc__/lib/esm5.js')))
.toEqual('export function FooB() {} // MODIFIED'); .toEqual('export function FooB() {} // MODIFIED');
expect(readFileSync('/node_modules/test/lib/esm5.js', 'utf8')) expect(fs.readFile(_('/node_modules/test/lib/esm5.js'))).toEqual('export function FooB() {}');
.toEqual('export function FooB() {}');
}); });
it('should also copy unmodified files in the program', () => { it('should also copy unmodified files in the program', () => {
@ -287,13 +269,13 @@ describe('NewEntryPointFileWriter', () => {
contents: 'export class FooB {} // MODIFIED' contents: 'export class FooB {} // MODIFIED'
}, },
]); ]);
expect(readFileSync('/node_modules/test/__ivy_ngcc__/lib/es2015/foo.js', 'utf8')) expect(fs.readFile(_('/node_modules/test/__ivy_ngcc__/lib/es2015/foo.js')))
.toEqual('export class FooB {} // MODIFIED'); .toEqual('export class FooB {} // MODIFIED');
expect(readFileSync('/node_modules/test/lib/es2015/foo.js', 'utf8')) expect(fs.readFile(_('/node_modules/test/lib/es2015/foo.js')))
.toEqual('import {FooA} from "test/a"; import "events"; export class FooB {}'); .toEqual('import {FooA} from "test/a"; import "events"; export class FooB {}');
expect(readFileSync('/node_modules/test/__ivy_ngcc__/lib/es2015/index.js', 'utf8')) expect(fs.readFile(_('/node_modules/test/__ivy_ngcc__/lib/es2015/index.js')))
.toEqual('export {FooB} from "./foo"; import * from "other";'); .toEqual('export {FooB} from "./foo"; import * from "other";');
expect(readFileSync('/node_modules/test/lib/es2015/index.js', 'utf8')) expect(fs.readFile(_('/node_modules/test/lib/es2015/index.js')))
.toEqual('export {FooB} from "./foo"; import * from "other";'); .toEqual('export {FooB} from "./foo"; import * from "other";');
}); });
@ -301,11 +283,11 @@ describe('NewEntryPointFileWriter', () => {
() => { () => {
fileWriter.writeBundle(entryPoint, esm2015bundle, [ fileWriter.writeBundle(entryPoint, esm2015bundle, [
{ {
path: '/node_modules/test/lib/es2015/foo.js', path: _('/node_modules/test/lib/es2015/foo.js'),
contents: 'export class FooB {} // MODIFIED' contents: 'export class FooB {} // MODIFIED'
}, },
]); ]);
expect(existsSync('/node_modules/test/__ivy_ngcc__/a/index.d.ts')).toEqual(false); expect(fs.exists(_('/node_modules/test/__ivy_ngcc__/a/index.d.ts'))).toEqual(false);
}); });
it('should not copy files outside of the package', () => { it('should not copy files outside of the package', () => {
@ -315,8 +297,8 @@ describe('NewEntryPointFileWriter', () => {
contents: 'export class FooB {} // MODIFIED' contents: 'export class FooB {} // MODIFIED'
}, },
]); ]);
expect(existsSync('/node_modules/test/other/index.d.ts')).toEqual(false); expect(fs.exists(_('/node_modules/test/other/index.d.ts'))).toEqual(false);
expect(existsSync('/node_modules/test/events/events.js')).toEqual(false); expect(fs.exists(_('/node_modules/test/events/events.js'))).toEqual(false);
}); });
it('should update the package.json properties', () => { it('should update the package.json properties', () => {
@ -326,7 +308,7 @@ describe('NewEntryPointFileWriter', () => {
contents: 'export function FooB() {} // MODIFIED' contents: 'export function FooB() {} // MODIFIED'
}, },
]); ]);
expect(loadPackageJson('/node_modules/test/b')).toEqual(jasmine.objectContaining({ expect(loadPackageJson(fs, '/node_modules/test/b')).toEqual(jasmine.objectContaining({
module_ivy_ngcc: '../__ivy_ngcc__/lib/esm5.js', module_ivy_ngcc: '../__ivy_ngcc__/lib/esm5.js',
})); }));
@ -336,7 +318,7 @@ describe('NewEntryPointFileWriter', () => {
contents: 'export class FooB {} // MODIFIED' contents: 'export class FooB {} // MODIFIED'
}, },
]); ]);
expect(loadPackageJson('/node_modules/test/b')).toEqual(jasmine.objectContaining({ expect(loadPackageJson(fs, '/node_modules/test/b')).toEqual(jasmine.objectContaining({
module_ivy_ngcc: '../__ivy_ngcc__/lib/esm5.js', module_ivy_ngcc: '../__ivy_ngcc__/lib/esm5.js',
es2015_ivy_ngcc: '../__ivy_ngcc__/lib/es2015/index.js', es2015_ivy_ngcc: '../__ivy_ngcc__/lib/es2015/index.js',
})); }));
@ -349,11 +331,11 @@ describe('NewEntryPointFileWriter', () => {
contents: 'export declare class FooB {} // MODIFIED' contents: 'export declare class FooB {} // MODIFIED'
}, },
]); ]);
expect(readFileSync('/node_modules/test/typings/index.d.ts', 'utf8')) expect(fs.readFile(_('/node_modules/test/typings/index.d.ts')))
.toEqual('export declare class FooB {} // MODIFIED'); .toEqual('export declare class FooB {} // MODIFIED');
expect(readFileSync('/node_modules/test/typings/index.d.ts.__ivy_ngcc_bak', 'utf8')) expect(fs.readFile(_('/node_modules/test/typings/index.d.ts.__ivy_ngcc_bak')))
.toEqual('export declare class FooB {}'); .toEqual('export declare class FooB {}');
expect(existsSync('/node_modules/test/__ivy_ngcc__/typings/index.d.ts')).toBe(false); expect(fs.exists(_('/node_modules/test/__ivy_ngcc__/typings/index.d.ts'))).toBe(false);
}); });
}); });
}); });