refactor(ivy): implement a virtual file-system layer in ngtsc + ngcc (#30921)
To improve cross platform support, all file access (and path manipulation) is now done through a well known interface (`FileSystem`). For testing a number of `MockFileSystem` implementations are provided. These provide an in-memory file-system which emulates operating systems like OS/X, Unix and Windows. The current file system is always available via the static method, `FileSystem.getFileSystem()`. This is also used by a number of static methods on `AbsoluteFsPath` and `PathSegment`, to avoid having to pass `FileSystem` objects around all the time. The result of this is that one must be careful to ensure that the file-system has been initialized before using any of these static methods. To prevent this happening accidentally the current file system always starts out as an instance of `InvalidFileSystem`, which will throw an error if any of its methods are called. You can set the current file-system by calling `FileSystem.setFileSystem()`. During testing you can call the helper function `initMockFileSystem(os)` which takes a string name of the OS to emulate, and will also monkey-patch aspects of the TypeScript library to ensure that TS is also using the current file-system. Finally there is the `NgtscCompilerHost` to be used for any TypeScript compilation, which uses a given file-system. All tests that interact with the file-system should be tested against each of the mock file-systems. A series of helpers have been provided to support such tests: * `runInEachFileSystem()` - wrap your tests in this helper to run all the wrapped tests in each of the mock file-systems. * `addTestFilesToFileSystem()` - use this to add files and their contents to the mock file system for testing. * `loadTestFilesFromDisk()` - use this to load a mirror image of files on disk into the in-memory mock file-system. * `loadFakeCore()` - use this to load a fake version of `@angular/core` into the mock file-system. All ngcc and ngtsc source and tests now use this virtual file-system setup. PR Close #30921
This commit is contained in:

committed by
Kara Erickson

parent
1e7e065423
commit
7186f9c016
@ -5,19 +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 * as ts from 'typescript';
|
||||
|
||||
import {AbsoluteFsPath} from '../../../src/ngtsc/path';
|
||||
import {makeProgram} from '../../../src/ngtsc/testing/in_memory_typescript';
|
||||
import {BundleProgram} from '../../src/packages/bundle_program';
|
||||
import {AbsoluteFsPath, NgtscCompilerHost, absoluteFrom, getFileSystem} from '../../../src/ngtsc/file_system';
|
||||
import {TestFile} from '../../../src/ngtsc/file_system/testing';
|
||||
import {BundleProgram, makeBundleProgram} from '../../src/packages/bundle_program';
|
||||
import {EntryPointFormat, EntryPointJsonProperty} from '../../src/packages/entry_point';
|
||||
import {EntryPointBundle} from '../../src/packages/entry_point_bundle';
|
||||
import {patchTsGetExpandoInitializer, restoreGetExpandoInitializer} from '../../src/packages/patch_ts_expando_initializer';
|
||||
import {Folder} from './mock_file_system';
|
||||
import {NgccSourcesCompilerHost} from '../../src/packages/ngcc_compiler_host';
|
||||
|
||||
export {getDeclaration} from '../../../src/ngtsc/testing/in_memory_typescript';
|
||||
|
||||
const _ = AbsoluteFsPath.fromUnchecked;
|
||||
/**
|
||||
*
|
||||
* @param format The format of the bundle.
|
||||
@ -26,86 +20,31 @@ const _ = AbsoluteFsPath.fromUnchecked;
|
||||
*/
|
||||
export function makeTestEntryPointBundle(
|
||||
formatProperty: EntryPointJsonProperty, format: EntryPointFormat, isCore: boolean,
|
||||
files: {name: string, contents: string, isRoot?: boolean}[],
|
||||
dtsFiles?: {name: string, contents: string, isRoot?: boolean}[]): EntryPointBundle {
|
||||
const src = makeTestBundleProgram(files);
|
||||
const dts = dtsFiles ? makeTestBundleProgram(dtsFiles) : null;
|
||||
srcRootNames: AbsoluteFsPath[], dtsRootNames?: AbsoluteFsPath[]): EntryPointBundle {
|
||||
const src = makeTestBundleProgram(srcRootNames[0], isCore);
|
||||
const dts = dtsRootNames ? makeTestDtsBundleProgram(dtsRootNames[0], isCore) : null;
|
||||
const isFlatCore = isCore && src.r3SymbolsFile === null;
|
||||
return {formatProperty, format, rootDirs: [_('/')], src, dts, isCore, isFlatCore};
|
||||
return {formatProperty, format, rootDirs: [absoluteFrom('/')], src, dts, isCore, isFlatCore};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a bundle program for testing.
|
||||
* @param files The source files of the bundle program.
|
||||
*/
|
||||
export function makeTestBundleProgram(files: {name: string, contents: string}[]): BundleProgram {
|
||||
const {program, options, host} = makeTestProgramInternal(...files);
|
||||
const path = _(files[0].name);
|
||||
const file = program.getSourceFile(path) !;
|
||||
const r3SymbolsInfo = files.find(file => file.name.indexOf('r3_symbols') !== -1) || null;
|
||||
const r3SymbolsPath = r3SymbolsInfo && _(r3SymbolsInfo.name);
|
||||
const r3SymbolsFile = r3SymbolsPath && program.getSourceFile(r3SymbolsPath) || null;
|
||||
return {program, options, host, path, file, r3SymbolsPath, r3SymbolsFile};
|
||||
export function makeTestBundleProgram(
|
||||
path: AbsoluteFsPath, isCore: boolean = false): BundleProgram {
|
||||
const fs = getFileSystem();
|
||||
const options = {allowJs: true, checkJs: false};
|
||||
const entryPointPath = fs.dirname(path);
|
||||
const host = new NgccSourcesCompilerHost(fs, options, entryPointPath);
|
||||
return makeBundleProgram(fs, isCore, path, 'r3_symbols.js', options, host);
|
||||
}
|
||||
|
||||
function makeTestProgramInternal(
|
||||
...files: {name: string, contents: string, isRoot?: boolean | undefined}[]): {
|
||||
program: ts.Program,
|
||||
host: ts.CompilerHost,
|
||||
options: ts.CompilerOptions,
|
||||
} {
|
||||
const originalTsGetExpandoInitializer = patchTsGetExpandoInitializer();
|
||||
const program =
|
||||
makeProgram([getFakeCore(), getFakeTslib(), ...files], {allowJs: true, checkJs: false});
|
||||
restoreGetExpandoInitializer(originalTsGetExpandoInitializer);
|
||||
return program;
|
||||
export function makeTestDtsBundleProgram(
|
||||
path: AbsoluteFsPath, isCore: boolean = false): BundleProgram {
|
||||
const fs = getFileSystem();
|
||||
const options = {};
|
||||
const host = new NgtscCompilerHost(fs, options);
|
||||
return makeBundleProgram(fs, isCore, path, 'r3_symbols.d.ts', options, host);
|
||||
}
|
||||
|
||||
export function makeTestProgram(
|
||||
...files: {name: string, contents: string, isRoot?: boolean | undefined}[]): ts.Program {
|
||||
return makeTestProgramInternal(...files).program;
|
||||
}
|
||||
|
||||
// TODO: unify this with the //packages/compiler-cli/test/ngtsc/fake_core package
|
||||
export function getFakeCore() {
|
||||
return {
|
||||
name: 'node_modules/@angular/core/index.d.ts',
|
||||
contents: `
|
||||
type FnWithArg<T> = (arg?: any) => T;
|
||||
|
||||
export declare const Component: FnWithArg<(clazz: any) => any>;
|
||||
export declare const Directive: FnWithArg<(clazz: any) => any>;
|
||||
export declare const Injectable: FnWithArg<(clazz: any) => any>;
|
||||
export declare const NgModule: FnWithArg<(clazz: any) => any>;
|
||||
|
||||
export declare const Input: any;
|
||||
|
||||
export declare const Inject: FnWithArg<(a: any, b: any, c: any) => void>;
|
||||
export declare const Self: FnWithArg<(a: any, b: any, c: any) => void>;
|
||||
export declare const SkipSelf: FnWithArg<(a: any, b: any, c: any) => void>;
|
||||
export declare const Optional: FnWithArg<(a: any, b: any, c: any) => void>;
|
||||
|
||||
export declare class InjectionToken {
|
||||
constructor(name: string);
|
||||
}
|
||||
|
||||
export declare interface ModuleWithProviders<T = any> {}
|
||||
`
|
||||
};
|
||||
}
|
||||
|
||||
export function getFakeTslib() {
|
||||
return {
|
||||
name: 'node_modules/tslib/index.d.ts',
|
||||
contents: `
|
||||
export declare function __decorate(decorators: any[], target: any, key?: string | symbol, desc?: any);
|
||||
export declare function __param(paramIndex: number, decorator: any);
|
||||
export declare function __metadata(metadataKey: any, metadataValue: any);
|
||||
`
|
||||
};
|
||||
}
|
||||
|
||||
export function convertToDirectTsLibImport(filesystem: {name: string, contents: string}[]) {
|
||||
export function convertToDirectTsLibImport(filesystem: TestFile[]) {
|
||||
return filesystem.map(file => {
|
||||
const contents =
|
||||
file.contents
|
||||
@ -117,10 +56,6 @@ export function convertToDirectTsLibImport(filesystem: {name: string, 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;
|
||||
export function getRootFiles(testFiles: TestFile[]): AbsoluteFsPath[] {
|
||||
return testFiles.filter(f => f.isRoot !== false).map(f => absoluteFrom(f.name));
|
||||
}
|
||||
|
Reference in New Issue
Block a user