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
@ -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;
|
||||
|
Reference in New Issue
Block a user