angular-cn/packages/compiler-cli/ngcc/test/integration/util.ts

126 lines
4.3 KiB
TypeScript

/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import * as ts from 'typescript';
import {FileSystem, getFileSystem} from '../../../src/ngtsc/file_system';
import {MockFileSystemPosix} from '../../../src/ngtsc/file_system/testing';
import {loadStandardTestFiles} from '../../../test/helpers';
export type NodeModulesDef = {
[name: string]: Package
};
export type Package = {
[path: string]: string;
};
/**
* Compile one or more testing packages into the top-level `FileSystem`.
*
* Instead of writing ESM5 code by hand, and manually describing the Angular Package Format
* structure of that code in a mock NPM package, `genNodeModules` allows for the generation of one
* or more NPM packages from TypeScript source code. Each named NPM package in `def` is
* independently transpiled with `compileNodeModuleToFs` and written into `node_modules` in the
* top-level filesystem, ready for use in testing ngcc.
*/
export function genNodeModules(def: NodeModulesDef): void {
const fs = getFileSystem();
for (const pkgName of Object.keys(def)) {
compileNodeModuleToFs(fs, pkgName, def[pkgName]);
}
}
/**
* Takes the TypeScript project defined in the `Package` structure, compiles it to ESM5, and sets it
* up as a package in `node_modules` in `fs`.
*
* TODO(alxhub): over time, expand this to other bundle formats and make it more faithful to the
* shape of real NPM packages.
*/
function compileNodeModuleToFs(fs: FileSystem, pkgName: string, pkg: Package): void {
const compileFs = new MockFileSystemPosix(true);
compileFs.init(loadStandardTestFiles({fakeCore: false}));
const options: ts.CompilerOptions = {
declaration: true,
module: ts.ModuleKind.ESNext,
target: ts.ScriptTarget.ES5,
lib: [],
};
const rootNames = Object.keys(pkg);
for (const fileName of rootNames) {
compileFs.writeFile(compileFs.resolve(fileName), pkg[fileName]);
}
const host = new MockCompilerHost(compileFs);
const program = ts.createProgram({host, rootNames, options});
program.emit();
// Copy over the JS and .d.ts files, and add a .metadata.json for each .d.ts file.
for (const inFileTs of rootNames) {
const inFileBase = inFileTs.replace(/\.ts$/, '');
fs.writeFile(
fs.resolve(`/node_modules/${pkgName}/${inFileBase}.d.ts`),
compileFs.readFile(compileFs.resolve(`${inFileBase}.d.ts`)));
const jsContents = compileFs.readFile(compileFs.resolve(`${inFileBase}.js`));
fs.writeFile(fs.resolve(`/node_modules/${pkgName}/${inFileBase}.js`), jsContents);
fs.writeFile(fs.resolve(`/node_modules/${pkgName}/${inFileBase}.metadata.json`), '{}');
}
// Write the package.json
const pkgJson: unknown = {
name: pkgName,
version: '0.0.1',
main: './index.js',
typings: './index.d.ts',
};
fs.writeFile(
fs.resolve(`/node_modules/${pkgName}/package.json`), JSON.stringify(pkgJson, null, 2));
}
/**
* A simple `ts.CompilerHost` that uses a `FileSystem` instead of the real FS.
*
* TODO(alxhub): convert this into a first class `FileSystemCompilerHost` and use it as the base for
* the entire compiler.
*/
class MockCompilerHost implements ts.CompilerHost {
constructor(private fs: FileSystem) {}
getSourceFile(
fileName: string, languageVersion: ts.ScriptTarget,
onError?: ((message: string) => void)|undefined,
shouldCreateNewSourceFile?: boolean|undefined): ts.SourceFile|undefined {
return ts.createSourceFile(
fileName, this.fs.readFile(this.fs.resolve(fileName)), languageVersion, true,
ts.ScriptKind.TS);
}
getDefaultLibFileName(options: ts.CompilerOptions): string {
return ts.getDefaultLibFileName(options);
}
writeFile(fileName: string, data: string): void {
this.fs.writeFile(this.fs.resolve(fileName), data);
}
getCurrentDirectory(): string { return this.fs.pwd(); }
getCanonicalFileName(fileName: string): string { return fileName; }
useCaseSensitiveFileNames(): boolean { return true; }
getNewLine(): string { return '\n'; }
fileExists(fileName: string): boolean { return this.fs.exists(this.fs.resolve(fileName)); }
readFile(fileName: string): string|undefined {
const abs = this.fs.resolve(fileName);
return this.fs.exists(abs) ? this.fs.readFile(abs) : undefined;
}
}