Files
Kristiyan Kostadinov b4fc0dace5 test(compiler): switch compliance tests to es2022
Sets up the compliance tests to target es2022 since that's the default in the CLI. Also updates all of the tests, primarily because the generated output for static properties has changed.

(cherry picked from commit 41eacff363)
2026-01-02 08:29:16 +01:00

183 lines
6.2 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.dev/license
*/
import ts from 'typescript';
import {
AbsoluteFsPath,
FileSystem,
PathManipulation,
ReadonlyFileSystem,
} from '../../../src/ngtsc/file_system';
import {initMockFileSystem} from '../../../src/ngtsc/file_system/testing';
import {
loadStandardTestFiles,
loadTestDirectory,
NgtscTestCompilerHost,
} from '../../../src/ngtsc/testing';
import {performCompilation} from '../../../src/perform_compile';
import {CompilerOptions} from '../../../src/transformers/api';
import {ConfigOptions} from './get_compliance_tests';
/**
* Setup a mock file-system that is used to generate the partial files.
*
* @param realTestPath Absolute path (on the real file-system) to the test case being processed.
* @returns a mock file-system containing the test case files.
*/
export function initMockTestFileSystem(realTestPath: AbsoluteFsPath): FileSystem {
const fs = initMockFileSystem('Native');
const testFiles = loadStandardTestFiles();
fs.init(testFiles);
loadTestDirectory(fs, realTestPath, getRootDirectory(fs));
monkeyPatchReadFile(fs);
return fs;
}
/** The result of compiling a test-case. */
export interface CompileResult {
emittedFiles: AbsoluteFsPath[];
errors: string[];
}
/**
* Compile the input source `files` stored in `fs`, writing the generated files to `fs`.
*
* @param fs The mock file-system where the input and generated files live.
* @param files An array of paths (relative to the testPath) of input files to be compiled.
* @param compilerOptions Any extra options to pass to the TypeScript compiler.
* @param angularCompilerOptions Any extra options to pass to the Angular compiler.
* @returns A collection of paths of the generated files (absolute within the mock file-system).
*/
export function compileTest(
fs: FileSystem,
files: string[],
compilerOptions: ConfigOptions | undefined,
angularCompilerOptions: ConfigOptions | undefined,
): CompileResult {
const rootDir = getRootDirectory(fs);
const outDir = getBuildOutputDirectory(fs);
const options = getOptions(rootDir, outDir, compilerOptions, angularCompilerOptions);
const rootNames = files.map((f) => fs.resolve(f));
const host = new NgtscTestCompilerHost(fs, options);
const {diagnostics, emitResult} = performCompilation({rootNames, host, options});
const emittedFiles = emitResult
? emitResult.emittedFiles!.map((p) => fs.resolve(rootDir, p))
: [];
const errors = parseDiagnostics(diagnostics);
return {errors, emittedFiles};
}
/**
* Gets an absolute path (in the mock file-system) of the root directory where the compilation is to
* be done.
*
* @param fs the mock file-system where the compilation is happening.
*/
export function getRootDirectory(fs: PathManipulation): AbsoluteFsPath {
return fs.resolve('/');
}
/**
* Gets an absolute path (in the mock file-system) of the directory where the compiled files are
* stored.
*
* @param fs the mock file-system where the compilation is happening.
*/
export function getBuildOutputDirectory(fs: PathManipulation): AbsoluteFsPath {
return fs.resolve('/built');
}
/**
* Get the options object to pass to the compiler.
*
* @param rootDir The absolute path (within the mock file-system) that is the root of the
* compilation.
* @param outDir The absolute path (within the mock file-system) where compiled files will be
* written.
* @param compilerOptions Additional options for the TypeScript compiler.
* @param angularCompilerOptions Additional options for the Angular compiler.
*/
function getOptions(
rootDir: AbsoluteFsPath,
outDir: AbsoluteFsPath,
compilerOptions: ConfigOptions | undefined,
angularCompilerOptions: ConfigOptions | undefined,
): CompilerOptions {
const convertedCompilerOptions = ts.convertCompilerOptionsFromJson(compilerOptions, rootDir);
if (convertedCompilerOptions.errors.length > 0) {
throw new Error(
'Invalid compilerOptions in test-case::\n' +
convertedCompilerOptions.errors.map((d) => d.messageText).join('\n'),
);
}
return {
emitDecoratorMetadata: true,
experimentalDecorators: true,
skipLibCheck: true,
noImplicitAny: true,
noEmitOnError: true,
listEmittedFiles: true,
strictNullChecks: true,
outDir,
rootDir,
baseUrl: '.',
allowJs: true,
declaration: true,
target: ts.ScriptTarget.ES2022,
newLine: ts.NewLineKind.LineFeed,
module: ts.ModuleKind.ES2015,
moduleResolution: ts.ModuleResolutionKind.Bundler,
typeRoots: ['node_modules/@types'],
...convertedCompilerOptions.options,
enableI18nLegacyMessageIdFormat: false,
...angularCompilerOptions,
};
}
/**
* Replace escaped line-ending markers (\r\n) with real line-ending characters.
*
* This allows us to simulate, more reliably, files that have `\r\n` line-endings.
* (See `test_cases/r3_view_compiler_i18n/line_ending_normalization/template.html`.)
*/
function monkeyPatchReadFile(fs: ReadonlyFileSystem): void {
const originalReadFile = fs.readFile;
fs.readFile = (path: AbsoluteFsPath): string => {
const file = originalReadFile.call(fs, path);
return (
file
// First convert actual `\r\n` sequences to `\n`
.replace(/\r\n/g, '\n')
// unescape `\r\n` at the end of a line
.replace(/\\r\\n\n/g, '\r\n')
// unescape `\\r\\n`, at the end of a line, to `\r\n`
.replace(/\\\\r\\\\n(\r?\n)/g, '\\r\\n$1')
);
};
}
/**
* Parse the `diagnostics` to extract an error message string.
*
* The error message includes the location if available.
*
* @param diagnostics The diagnostics to parse.
*/
function parseDiagnostics(diagnostics: readonly ts.Diagnostic[]): string[] {
return diagnostics.map((diagnostic) => {
const message = ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n');
if ('file' in diagnostic && diagnostic.file !== undefined && diagnostic.start !== undefined) {
const {line, character} = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start);
return `${diagnostic.file.fileName} (${line + 1},${character + 1}): ${message}`;
} else {
return message;
}
});
}