The compiler exports a `formatDiagnostics` function which consumers can use to print both ts and ng diagnostics. However, this function was previously using the "old" style TypeScript diagnostics, as opposed to the modern diagnostic printer which uses terminal colors and prints additional context information. This commit updates `formatDiagnostics` to use the modern formatter, plus to update Ivy's negative error codes to Angular 'NG' errors. The Angular CLI needs a little more work to use this function for printing TS diagnostics, but this commit alone should fix Bazel builds as ngc-wrapped goes through `formatDiagnostics`. PR Close #34234
180 lines
6.0 KiB
TypeScript
180 lines
6.0 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
|
|
*/
|
|
/// <reference types="node" />
|
|
import * as fs from 'fs';
|
|
import * as path from 'path';
|
|
import * as ts from 'typescript';
|
|
|
|
import * as ng from '../index';
|
|
import {NodeJSFileSystem, setFileSystem} from '../src/ngtsc/file_system';
|
|
import {getAngularPackagesFromRunfiles, resolveNpmTreeArtifact} from '../test/helpers';
|
|
|
|
// 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);
|
|
|
|
getAngularPackagesFromRunfiles().forEach(({pkgPath, name}) => {
|
|
fs.symlinkSync(pkgPath, path.join(angularDirectory, name), 'junction');
|
|
});
|
|
|
|
// Link typescript
|
|
const typeScriptSource = resolveNpmTreeArtifact('npm/node_modules/typescript');
|
|
const typescriptDest = path.join(nodeModulesPath, 'typescript');
|
|
fs.symlinkSync(typeScriptSource, typescriptDest, 'junction');
|
|
|
|
// 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, '');
|
|
}
|