mirror of
https://github.com/angular/angular
synced 2026-05-24 09:28:37 +00:00
As outlined in the previous commit which enabled the `esModuleInterop` TypeScript compiler option, we need to update all namespace imports for `typescript` to default imports. This is needed to allow for TypeScript to be imported at runtime from an ES module. Similar changes are needed for modules like `semver` where the types incorrectly suggest named exports that will not exist at runtime when imported from ESM. This commit refactors all imports to match with the lint rule we have configured in the previous commit. See the previous commit for more details on why certain imports have been changed. A special case are the imports to `@babel/core` and `@babel/types`. For these a special interop is needed as both default imports, or named imports break the other module format. e.g default imports would work well for ESM, but it breaks for CJS. For CJS, the named imports would only work, but in ESM, only the default export exist. We work around this for now until the devmode is using ESM as well (which would be consistent with prodmode and gives us more valuable test results). More details on the interop can be found in the `babel_core.ts` files (two interops are needed for both localize/or the compiler-cli). PR Close #43431
198 lines
6.9 KiB
TypeScript
198 lines
6.9 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright Google LLC 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
|
|
*/
|
|
/// <reference types="node" />
|
|
import * as fs from 'fs';
|
|
import * as path from 'path';
|
|
import ts from 'typescript';
|
|
|
|
import * as ng from '../index';
|
|
import {NodeJSFileSystem, setFileSystem} from '../src/ngtsc/file_system';
|
|
import {getAngularPackagesFromRunfiles, resolveNpmTreeArtifact} from '../src/ngtsc/testing';
|
|
|
|
// TEST_TMPDIR is always set by Bazel.
|
|
const tmpdir = process.env.TEST_TMPDIR!;
|
|
|
|
export function makeTempDir(): string {
|
|
let dir: string;
|
|
while (true) {
|
|
const id = (Math.random() * 1000000).toFixed(0);
|
|
dir = path.posix.join(tmpdir, `tmp.${id}`);
|
|
if (!fs.existsSync(dir)) break;
|
|
}
|
|
fs.mkdirSync(dir);
|
|
return dir;
|
|
}
|
|
|
|
export interface TestSupport {
|
|
basePath: string;
|
|
write(fileName: string, content: string): void;
|
|
writeFiles(...mockDirs: {[fileName: string]: string}[]): void;
|
|
createCompilerOptions(overrideOptions?: ng.CompilerOptions): ng.CompilerOptions;
|
|
shouldExist(fileName: string): void;
|
|
shouldNotExist(fileName: string): void;
|
|
}
|
|
|
|
function createTestSupportFor(basePath: string) {
|
|
// Typescript uses identity comparison on `paths` and other arrays in order to determine
|
|
// if program structure can be reused for incremental compilation, so we reuse the default
|
|
// values unless overriden, and freeze them so that they can't be accidentaly changed somewhere
|
|
// in tests.
|
|
const defaultCompilerOptions = {
|
|
basePath,
|
|
'experimentalDecorators': true,
|
|
'skipLibCheck': true,
|
|
'strict': true,
|
|
'strictPropertyInitialization': false,
|
|
'types': Object.freeze<string>([]) as string[],
|
|
'outDir': path.resolve(basePath, 'built'),
|
|
'rootDir': basePath,
|
|
'baseUrl': basePath,
|
|
'declaration': true,
|
|
'target': ts.ScriptTarget.ES5,
|
|
'newLine': ts.NewLineKind.LineFeed,
|
|
'module': ts.ModuleKind.ES2015,
|
|
'moduleResolution': ts.ModuleResolutionKind.NodeJs,
|
|
'enableIvy': false,
|
|
'lib': Object.freeze([
|
|
path.resolve(basePath, 'node_modules/typescript/lib/lib.es6.d.ts'),
|
|
]) as string[],
|
|
// clang-format off
|
|
'paths': Object.freeze({'@angular/*': ['./node_modules/@angular/*']}) as {[index: string]: string[]}
|
|
// clang-format on
|
|
};
|
|
|
|
|
|
return {
|
|
// We normalize the basePath into a posix path, so that multiple assertions which compare
|
|
// paths don't need to normalize the path separators each time.
|
|
basePath: normalizeSeparators(basePath),
|
|
write,
|
|
writeFiles,
|
|
createCompilerOptions,
|
|
shouldExist,
|
|
shouldNotExist
|
|
};
|
|
|
|
function ensureDirExists(absolutePathToDir: string) {
|
|
if (fs.existsSync(absolutePathToDir)) {
|
|
if (!fs.statSync(absolutePathToDir).isDirectory()) {
|
|
throw new Error(`'${absolutePathToDir}' exists and is not a directory.`);
|
|
}
|
|
} else {
|
|
const parentDir = path.dirname(absolutePathToDir);
|
|
ensureDirExists(parentDir);
|
|
fs.mkdirSync(absolutePathToDir);
|
|
}
|
|
}
|
|
|
|
function write(fileName: string, content: string) {
|
|
const absolutePathToFile = path.resolve(basePath, fileName);
|
|
ensureDirExists(path.dirname(absolutePathToFile));
|
|
fs.writeFileSync(absolutePathToFile, content);
|
|
}
|
|
|
|
function writeFiles(...mockDirs: {[fileName: string]: string}[]) {
|
|
mockDirs.forEach((dir) => {
|
|
Object.keys(dir).forEach((fileName) => {
|
|
write(fileName, dir[fileName]);
|
|
});
|
|
});
|
|
}
|
|
|
|
function createCompilerOptions(overrideOptions: ng.CompilerOptions = {}): ng.CompilerOptions {
|
|
return {...defaultCompilerOptions, ...overrideOptions};
|
|
}
|
|
|
|
function shouldExist(fileName: string) {
|
|
if (!fs.existsSync(path.resolve(basePath, fileName))) {
|
|
throw new Error(`Expected ${fileName} to be emitted (basePath: ${basePath})`);
|
|
}
|
|
}
|
|
|
|
function shouldNotExist(fileName: string) {
|
|
if (fs.existsSync(path.resolve(basePath, fileName))) {
|
|
throw new Error(`Did not expect ${fileName} to be emitted (basePath: ${basePath})`);
|
|
}
|
|
}
|
|
}
|
|
|
|
export function setupBazelTo(tmpDirPath: string) {
|
|
const nodeModulesPath = path.join(tmpDirPath, 'node_modules');
|
|
const angularDirectory = path.join(nodeModulesPath, '@angular');
|
|
|
|
fs.mkdirSync(nodeModulesPath);
|
|
fs.mkdirSync(angularDirectory);
|
|
|
|
function linkNpmArtifact(artifact: string, target: string = artifact) {
|
|
try {
|
|
const source = resolveNpmTreeArtifact(`npm/node_modules/${artifact}`);
|
|
const dest = path.join(nodeModulesPath, target);
|
|
fs.symlinkSync(source, dest, 'junction');
|
|
} catch (e) {
|
|
// Allow a module to be missing as some Bazel targets do not need all artifacts.
|
|
if (e.code !== 'MODULE_NOT_FOUND') throw e;
|
|
}
|
|
}
|
|
|
|
// Link @angular packages. These reference the v12 packages from NPM as the ViewEngine tests
|
|
// can no longer depend on `npm_package` Bazel targets, given that the `ng_module` to produce
|
|
// them is no longer capable of producing ViewEngine packages. As a result, the `npm_package`
|
|
// outputs end up being compiled using Ivy partial compilation format which is not suitable to be
|
|
// used in ViewEngine ngc tests.
|
|
linkNpmArtifact('@angular/core-12', '@angular/core');
|
|
linkNpmArtifact('@angular/common-12', '@angular/common');
|
|
linkNpmArtifact('@angular/router-12', '@angular/router');
|
|
linkNpmArtifact('@angular/forms-12', '@angular/forms');
|
|
linkNpmArtifact('@angular/platform-browser-12', '@angular/platform-browser');
|
|
|
|
// Link typescript
|
|
linkNpmArtifact('typescript');
|
|
|
|
// Link "rxjs" if it has been set up as a runfile. "rxjs" is linked optionally because
|
|
// not all compiler-cli tests need "rxjs" set up.
|
|
try {
|
|
const rxjsSource = resolveNpmTreeArtifact('rxjs', 'index.js');
|
|
const rxjsDest = path.join(nodeModulesPath, 'rxjs');
|
|
fs.symlinkSync(rxjsSource, rxjsDest, 'junction');
|
|
} catch (e) {
|
|
if (e.code !== 'MODULE_NOT_FOUND') throw e;
|
|
}
|
|
}
|
|
|
|
export function setup(): TestSupport {
|
|
// // `TestSupport` provides its own file-system abstraction so we just use
|
|
// // the native `NodeJSFileSystem` under the hood.
|
|
setFileSystem(new NodeJSFileSystem());
|
|
const tmpDirPath = makeTempDir();
|
|
setupBazelTo(tmpDirPath);
|
|
return createTestSupportFor(tmpDirPath);
|
|
}
|
|
|
|
export function expectNoDiagnostics(options: ng.CompilerOptions, diags: ng.Diagnostics) {
|
|
const errorDiags = diags.filter(d => d.category !== ts.DiagnosticCategory.Message);
|
|
if (errorDiags.length) {
|
|
throw new Error(`Expected no diagnostics: ${ng.formatDiagnostics(errorDiags)}`);
|
|
}
|
|
}
|
|
|
|
export function expectNoDiagnosticsInProgram(options: ng.CompilerOptions, p: ng.Program) {
|
|
expectNoDiagnostics(options, [
|
|
...p.getNgStructuralDiagnostics(), ...p.getTsSemanticDiagnostics(),
|
|
...p.getNgSemanticDiagnostics()
|
|
]);
|
|
}
|
|
|
|
export function normalizeSeparators(path: string): string {
|
|
return path.replace(/\\/g, '/');
|
|
}
|
|
|
|
const STRIP_ANSI = /\x1B\x5B\d+m/g;
|
|
|
|
export function stripAnsi(diags: string): string {
|
|
return diags.replace(STRIP_ANSI, '');
|
|
}
|