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:
Pete Bacon Darwin
2019-06-06 20:22:32 +01:00
committed by Kara Erickson
parent 1e7e065423
commit 7186f9c016
177 changed files with 16598 additions and 14829 deletions

View File

@ -6,17 +6,13 @@
* found in the LICENSE file at https://angular.io/license
*/
import {Position, isSyntaxError, syntaxError} from '@angular/compiler';
import * as fs from 'fs';
import * as path from 'path';
import {Position, isSyntaxError} from '@angular/compiler';
import * as ts from 'typescript';
import {AbsoluteFsPath, absoluteFrom, getFileSystem, relative, resolve} from '../src/ngtsc/file_system';
import * as api from './transformers/api';
import * as ng from './transformers/entry_points';
import {createMessageDiagnostic} from './transformers/util';
const TS_EXT = /\.ts$/;
export type Diagnostics = ReadonlyArray<ts.Diagnostic|api.Diagnostic>;
export function filterErrorsAndWarnings(diagnostics: Diagnostics): Diagnostics {
@ -30,7 +26,8 @@ const defaultFormatHost: ts.FormatDiagnosticsHost = {
};
function displayFileName(fileName: string, host: ts.FormatDiagnosticsHost): string {
return path.relative(host.getCurrentDirectory(), host.getCanonicalFileName(fileName));
return relative(
resolve(host.getCurrentDirectory()), resolve(host.getCanonicalFileName(fileName)));
}
export function formatDiagnosticPosition(
@ -110,11 +107,13 @@ export interface ParsedConfiguration {
}
export function calcProjectFileAndBasePath(project: string):
{projectFile: string, basePath: string} {
const projectIsDir = fs.lstatSync(project).isDirectory();
const projectFile = projectIsDir ? path.join(project, 'tsconfig.json') : project;
const projectDir = projectIsDir ? project : path.dirname(project);
const basePath = path.resolve(process.cwd(), projectDir);
{projectFile: AbsoluteFsPath, basePath: AbsoluteFsPath} {
const fs = getFileSystem();
const absProject = fs.resolve(project);
const projectIsDir = fs.lstat(absProject).isDirectory();
const projectFile = projectIsDir ? fs.join(absProject, 'tsconfig.json') : absProject;
const projectDir = projectIsDir ? absProject : fs.dirname(absProject);
const basePath = fs.resolve(projectDir);
return {projectFile, basePath};
}
@ -130,6 +129,7 @@ export function createNgCompilerOptions(
export function readConfiguration(
project: string, existingOptions?: ts.CompilerOptions): ParsedConfiguration {
try {
const fs = getFileSystem();
const {projectFile, basePath} = calcProjectFileAndBasePath(project);
const readExtendedConfigFile =
@ -149,11 +149,12 @@ export function readConfiguration(
}
if (config.extends) {
let extendedConfigPath = path.resolve(path.dirname(configFile), config.extends);
extendedConfigPath = path.extname(extendedConfigPath) ? extendedConfigPath :
`${extendedConfigPath}.json`;
let extendedConfigPath = fs.resolve(fs.dirname(configFile), config.extends);
extendedConfigPath = fs.extname(extendedConfigPath) ?
extendedConfigPath :
absoluteFrom(`${extendedConfigPath}.json`);
if (fs.existsSync(extendedConfigPath)) {
if (fs.exists(extendedConfigPath)) {
// Call read config recursively as TypeScript only merges CompilerOptions
return readExtendedConfigFile(extendedConfigPath, baseConfig);
}
@ -175,14 +176,14 @@ export function readConfiguration(
}
const parseConfigHost = {
useCaseSensitiveFileNames: true,
fileExists: fs.existsSync,
fileExists: fs.exists.bind(fs),
readDirectory: ts.sys.readDirectory,
readFile: ts.sys.readFile
};
const configFileName = path.resolve(process.cwd(), projectFile);
const configFileName = fs.resolve(fs.pwd(), projectFile);
const parsed = ts.parseJsonConfigFileContent(
config, parseConfigHost, basePath, existingOptions, configFileName);
const rootNames = parsed.fileNames.map(f => path.normalize(f));
const rootNames = parsed.fileNames;
const options = createNgCompilerOptions(basePath, config, parsed.options);
let emitFlags = api.EmitFlags.Default;