mirror of
https://github.com/microsoft/TypeScript.git
synced 2025-11-18 17:21:48 +00:00
* Add moduleDetection compiler flag to allow for changing how modules are parsed The default setting is 'auto', where JSX containing files under react-jsx and react-jsxdev are always parsed as modules, and esm-format files under module: node12+ are always parsed as modules, in addition to the 'legacy' detection mode's conditions for other files. (Declaration files are exempt from these new conditions) The 'legacy' mode preserves TS's behavior prior to the introduction of this flag - a file is parsed as a module if it contains an import, export, or import.meta expression. In addition, there is a 'force' mode that forces all non-declaration files to be parsed as modules. (Declaration files are still only modules if they contain a top-level import or export.) This technically breaks the parser API, but it's kinda-sorta backwards compatible so long as you don't need the functionality associated with more recent compiler flags. * Fix post-merge lint * Rename function * Update default value documentation * PR feedback * Fix lint and typo
341 lines
15 KiB
TypeScript
341 lines
15 KiB
TypeScript
namespace Harness {
|
|
export const enum CompilerTestType {
|
|
Conformance,
|
|
Regressions,
|
|
Test262
|
|
}
|
|
|
|
interface CompilerFileBasedTest extends FileBasedTest {
|
|
readonly content?: string;
|
|
}
|
|
|
|
export class CompilerBaselineRunner extends RunnerBase {
|
|
private basePath = "tests/cases";
|
|
private testSuiteName: TestRunnerKind;
|
|
private emit: boolean;
|
|
|
|
public options: string | undefined;
|
|
|
|
constructor(public testType: CompilerTestType) {
|
|
super();
|
|
this.emit = true;
|
|
if (testType === CompilerTestType.Conformance) {
|
|
this.testSuiteName = "conformance";
|
|
}
|
|
else if (testType === CompilerTestType.Regressions) {
|
|
this.testSuiteName = "compiler";
|
|
}
|
|
else if (testType === CompilerTestType.Test262) {
|
|
this.testSuiteName = "test262";
|
|
}
|
|
else {
|
|
this.testSuiteName = "compiler"; // default to this for historical reasons
|
|
}
|
|
this.basePath += "/" + this.testSuiteName;
|
|
}
|
|
|
|
public kind() {
|
|
return this.testSuiteName;
|
|
}
|
|
|
|
public enumerateTestFiles() {
|
|
// see also: `enumerateTestFiles` in tests/webTestServer.ts
|
|
return this.enumerateFiles(this.basePath, /\.tsx?$/, { recursive: true }).map(CompilerTest.getConfigurations);
|
|
}
|
|
|
|
public initializeTests() {
|
|
describe(this.testSuiteName + " tests", () => {
|
|
describe("Setup compiler for compiler baselines", () => {
|
|
this.parseOptions();
|
|
});
|
|
|
|
// this will set up a series of describe/it blocks to run between the setup and cleanup phases
|
|
const files = this.tests.length > 0 ? this.tests : IO.enumerateTestFiles(this);
|
|
files.forEach(test => {
|
|
const file = typeof test === "string" ? test : test.file;
|
|
this.checkTestCodeOutput(vpath.normalizeSeparators(file), typeof test === "string" ? CompilerTest.getConfigurations(test) : test);
|
|
});
|
|
});
|
|
}
|
|
|
|
public checkTestCodeOutput(fileName: string, test?: CompilerFileBasedTest) {
|
|
if (test && ts.some(test.configurations)) {
|
|
test.configurations.forEach(configuration => {
|
|
describe(`${this.testSuiteName} tests for ${fileName}${configuration ? ` (${getFileBasedTestConfigurationDescription(configuration)})` : ``}`, () => {
|
|
this.runSuite(fileName, test, configuration);
|
|
});
|
|
});
|
|
}
|
|
else {
|
|
describe(`${this.testSuiteName} tests for ${fileName}`, () => {
|
|
this.runSuite(fileName, test);
|
|
});
|
|
}
|
|
}
|
|
|
|
private runSuite(fileName: string, test?: CompilerFileBasedTest, configuration?: FileBasedTestConfiguration) {
|
|
// Mocha holds onto the closure environment of the describe callback even after the test is done.
|
|
// Everything declared here should be cleared out in the "after" callback.
|
|
let compilerTest!: CompilerTest;
|
|
before(() => {
|
|
let payload;
|
|
if (test && test.content) {
|
|
const rootDir = test.file.indexOf("conformance") === -1 ? "tests/cases/compiler/" : ts.getDirectoryPath(test.file) + "/";
|
|
payload = TestCaseParser.makeUnitsFromTest(test.content, test.file, rootDir);
|
|
}
|
|
compilerTest = new CompilerTest(fileName, payload, configuration);
|
|
});
|
|
it(`Correct errors for ${fileName}`, () => compilerTest.verifyDiagnostics());
|
|
it(`Correct module resolution tracing for ${fileName}`, () => compilerTest.verifyModuleResolution());
|
|
it(`Correct sourcemap content for ${fileName}`, () => compilerTest.verifySourceMapRecord());
|
|
it(`Correct JS output for ${fileName}`, () => (this.emit && compilerTest.verifyJavaScriptOutput()));
|
|
it(`Correct Sourcemap output for ${fileName}`, () => compilerTest.verifySourceMapOutput());
|
|
it(`Correct type/symbol baselines for ${fileName}`, () => compilerTest.verifyTypesAndSymbols());
|
|
after(() => {
|
|
compilerTest = undefined!;
|
|
});
|
|
}
|
|
|
|
private parseOptions() {
|
|
if (this.options && this.options.length > 0) {
|
|
this.emit = false;
|
|
|
|
const opts = this.options.split(",");
|
|
for (const opt of opts) {
|
|
switch (opt) {
|
|
case "emit":
|
|
this.emit = true;
|
|
break;
|
|
default:
|
|
throw new Error("unsupported flag");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
class CompilerTest {
|
|
private static varyBy: readonly string[] = [
|
|
"module",
|
|
"moduleResolution",
|
|
"moduleDetection",
|
|
"target",
|
|
"jsx",
|
|
"removeComments",
|
|
"importHelpers",
|
|
"importHelpers",
|
|
"downlevelIteration",
|
|
"isolatedModules",
|
|
"strict",
|
|
"noImplicitAny",
|
|
"strictNullChecks",
|
|
"strictFunctionTypes",
|
|
"strictBindCallApply",
|
|
"strictPropertyInitialization",
|
|
"noImplicitThis",
|
|
"alwaysStrict",
|
|
"allowSyntheticDefaultImports",
|
|
"esModuleInterop",
|
|
"emitDecoratorMetadata",
|
|
"skipDefaultLibCheck",
|
|
"preserveConstEnums",
|
|
"skipLibCheck",
|
|
"exactOptionalPropertyTypes",
|
|
"useUnknownInCatchVariables"
|
|
];
|
|
private fileName: string;
|
|
private justName: string;
|
|
private configuredName: string;
|
|
private lastUnit: TestCaseParser.TestUnitData;
|
|
private harnessSettings: TestCaseParser.CompilerSettings;
|
|
private hasNonDtsFiles: boolean;
|
|
private result: compiler.CompilationResult;
|
|
private options: ts.CompilerOptions;
|
|
private tsConfigFiles: Compiler.TestFile[];
|
|
// equivalent to the files that will be passed on the command line
|
|
private toBeCompiled: Compiler.TestFile[];
|
|
// equivalent to other files on the file system not directly passed to the compiler (ie things that are referenced by other files)
|
|
private otherFiles: Compiler.TestFile[];
|
|
|
|
constructor(fileName: string, testCaseContent?: TestCaseParser.TestCaseContent, configurationOverrides?: TestCaseParser.CompilerSettings) {
|
|
this.fileName = fileName;
|
|
this.justName = vpath.basename(fileName);
|
|
this.configuredName = this.justName;
|
|
if (configurationOverrides) {
|
|
let configuredName = "";
|
|
const keys = Object
|
|
.keys(configurationOverrides)
|
|
.sort();
|
|
for (const key of keys) {
|
|
if (configuredName) {
|
|
configuredName += ",";
|
|
}
|
|
configuredName += `${key.toLowerCase()}=${configurationOverrides[key].toLowerCase()}`;
|
|
}
|
|
if (configuredName) {
|
|
const extname = vpath.extname(this.justName);
|
|
const basename = vpath.basename(this.justName, extname, /*ignoreCase*/ true);
|
|
this.configuredName = `${basename}(${configuredName})${extname}`;
|
|
}
|
|
}
|
|
|
|
const rootDir = fileName.indexOf("conformance") === -1 ? "tests/cases/compiler/" : ts.getDirectoryPath(fileName) + "/";
|
|
|
|
if (testCaseContent === undefined) {
|
|
testCaseContent = TestCaseParser.makeUnitsFromTest(IO.readFile(fileName)!, fileName, rootDir);
|
|
}
|
|
|
|
if (configurationOverrides) {
|
|
testCaseContent = { ...testCaseContent, settings: { ...testCaseContent.settings, ...configurationOverrides } };
|
|
}
|
|
|
|
const units = testCaseContent.testUnitData;
|
|
this.harnessSettings = testCaseContent.settings;
|
|
let tsConfigOptions: ts.CompilerOptions | undefined;
|
|
this.tsConfigFiles = [];
|
|
if (testCaseContent.tsConfig) {
|
|
assert.equal(testCaseContent.tsConfig.fileNames.length, 0, `list of files in tsconfig is not currently supported`);
|
|
assert.equal(testCaseContent.tsConfig.raw.exclude, undefined, `exclude in tsconfig is not currently supported`);
|
|
|
|
tsConfigOptions = ts.cloneCompilerOptions(testCaseContent.tsConfig.options);
|
|
this.tsConfigFiles.push(this.createHarnessTestFile(testCaseContent.tsConfigFileUnitData!, rootDir, ts.combinePaths(rootDir, tsConfigOptions.configFilePath)));
|
|
}
|
|
else {
|
|
const baseUrl = this.harnessSettings.baseUrl;
|
|
if (baseUrl !== undefined && !ts.isRootedDiskPath(baseUrl)) {
|
|
this.harnessSettings.baseUrl = ts.getNormalizedAbsolutePath(baseUrl, rootDir);
|
|
}
|
|
}
|
|
|
|
this.lastUnit = units[units.length - 1];
|
|
this.hasNonDtsFiles = units.some(unit => !ts.fileExtensionIs(unit.name, ts.Extension.Dts));
|
|
// We need to assemble the list of input files for the compiler and other related files on the 'filesystem' (ie in a multi-file test)
|
|
// If the last file in a test uses require or a triple slash reference we'll assume all other files will be brought in via references,
|
|
// otherwise, assume all files are just meant to be in the same compilation session without explicit references to one another.
|
|
this.toBeCompiled = [];
|
|
this.otherFiles = [];
|
|
|
|
if (testCaseContent.settings.noImplicitReferences || /require\(/.test(this.lastUnit.content) || /reference\spath/.test(this.lastUnit.content)) {
|
|
this.toBeCompiled.push(this.createHarnessTestFile(this.lastUnit, rootDir));
|
|
units.forEach(unit => {
|
|
if (unit.name !== this.lastUnit.name) {
|
|
this.otherFiles.push(this.createHarnessTestFile(unit, rootDir));
|
|
}
|
|
});
|
|
}
|
|
else {
|
|
this.toBeCompiled = units.map(unit => {
|
|
return this.createHarnessTestFile(unit, rootDir);
|
|
});
|
|
}
|
|
|
|
if (tsConfigOptions && tsConfigOptions.configFilePath !== undefined) {
|
|
tsConfigOptions.configFilePath = ts.combinePaths(rootDir, tsConfigOptions.configFilePath);
|
|
tsConfigOptions.configFile!.fileName = tsConfigOptions.configFilePath;
|
|
}
|
|
|
|
this.result = Compiler.compileFiles(
|
|
this.toBeCompiled,
|
|
this.otherFiles,
|
|
this.harnessSettings,
|
|
/*options*/ tsConfigOptions,
|
|
/*currentDirectory*/ this.harnessSettings.currentDirectory,
|
|
testCaseContent.symlinks
|
|
);
|
|
|
|
this.options = this.result.options;
|
|
}
|
|
|
|
public static getConfigurations(file: string): CompilerFileBasedTest {
|
|
// also see `parseCompilerTestConfigurations` in tests/webTestServer.ts
|
|
const content = IO.readFile(file)!;
|
|
const settings = TestCaseParser.extractCompilerSettings(content);
|
|
const configurations = getFileBasedTestConfigurations(settings, CompilerTest.varyBy);
|
|
return { file, configurations, content };
|
|
}
|
|
|
|
public verifyDiagnostics() {
|
|
// check errors
|
|
Compiler.doErrorBaseline(
|
|
this.configuredName,
|
|
this.tsConfigFiles.concat(this.toBeCompiled, this.otherFiles),
|
|
this.result.diagnostics,
|
|
!!this.options.pretty);
|
|
}
|
|
|
|
public verifyModuleResolution() {
|
|
if (this.options.traceResolution) {
|
|
Baseline.runBaseline(this.configuredName.replace(/\.tsx?$/, ".trace.json"),
|
|
JSON.stringify(this.result.traces.map(Utils.sanitizeTraceResolutionLogEntry), undefined, 4));
|
|
}
|
|
}
|
|
|
|
public verifySourceMapRecord() {
|
|
if (this.options.sourceMap || this.options.inlineSourceMap || this.options.declarationMap) {
|
|
const record = Utils.removeTestPathPrefixes(this.result.getSourceMapRecord()!);
|
|
const baseline = (this.options.noEmitOnError && this.result.diagnostics.length !== 0) || record === undefined
|
|
// Because of the noEmitOnError option no files are created. We need to return null because baselining isn't required.
|
|
? null // eslint-disable-line no-null/no-null
|
|
: record;
|
|
Baseline.runBaseline(this.configuredName.replace(/\.tsx?$/, ".sourcemap.txt"), baseline);
|
|
}
|
|
}
|
|
|
|
public verifyJavaScriptOutput() {
|
|
if (this.hasNonDtsFiles) {
|
|
Compiler.doJsEmitBaseline(
|
|
this.configuredName,
|
|
this.fileName,
|
|
this.options,
|
|
this.result,
|
|
this.tsConfigFiles,
|
|
this.toBeCompiled,
|
|
this.otherFiles,
|
|
this.harnessSettings);
|
|
}
|
|
}
|
|
|
|
public verifySourceMapOutput() {
|
|
Compiler.doSourcemapBaseline(
|
|
this.configuredName,
|
|
this.options,
|
|
this.result,
|
|
this.harnessSettings);
|
|
}
|
|
|
|
public verifyTypesAndSymbols() {
|
|
if (this.fileName.indexOf("APISample") >= 0) {
|
|
return;
|
|
}
|
|
|
|
const noTypesAndSymbols =
|
|
this.harnessSettings.noTypesAndSymbols &&
|
|
this.harnessSettings.noTypesAndSymbols.toLowerCase() === "true";
|
|
if (noTypesAndSymbols) {
|
|
return;
|
|
}
|
|
|
|
Compiler.doTypeAndSymbolBaseline(
|
|
this.configuredName,
|
|
this.result.program!,
|
|
this.toBeCompiled.concat(this.otherFiles).filter(file => !!this.result.program!.getSourceFile(file.unitName)),
|
|
/*opts*/ undefined,
|
|
/*multifile*/ undefined,
|
|
/*skipTypeBaselines*/ undefined,
|
|
/*skipSymbolBaselines*/ undefined,
|
|
!!ts.length(this.result.diagnostics)
|
|
);
|
|
}
|
|
|
|
private makeUnitName(name: string, root: string) {
|
|
const path = ts.toPath(name, root, ts.identity);
|
|
const pathStart = ts.toPath(IO.getCurrentDirectory(), "", ts.identity);
|
|
return pathStart ? path.replace(pathStart, "/") : path;
|
|
}
|
|
|
|
private createHarnessTestFile(lastUnit: TestCaseParser.TestUnitData, rootDir: string, unitName?: string): Compiler.TestFile {
|
|
return { unitName: unitName || this.makeUnitName(lastUnit.name, rootDir), content: lastUnit.content, fileOptions: lastUnit.fileOptions };
|
|
}
|
|
}
|
|
}
|