diff --git a/src/harness/compilerRunner.ts b/src/harness/compilerRunner.ts index 88e81ffcf348d..a64435b7ddcbe 100644 --- a/src/harness/compilerRunner.ts +++ b/src/harness/compilerRunner.ts @@ -1,8 +1,6 @@ /// /// /// -// In harness baselines, null is different than undefined. See `generateActual` in `harness.ts`. -/* tslint:disable:no-null-keyword */ const enum CompilerTestType { Conformance, @@ -136,21 +134,10 @@ class CompilerBaselineRunner extends RunnerBase { otherFiles = undefined; }); - function getByteOrderMarkText(file: Harness.Compiler.GeneratedFile): string { - return file.writeByteOrderMark ? "\u00EF\u00BB\u00BF" : ""; - } - - function getErrorBaseline(toBeCompiled: Harness.Compiler.TestFile[], otherFiles: Harness.Compiler.TestFile[], result: Harness.Compiler.CompilerResult) { - return Harness.Compiler.getErrorBaseline(toBeCompiled.concat(otherFiles), result.errors); - } - // check errors it("Correct errors for " + fileName, () => { if (this.errors) { - Harness.Baseline.runBaseline(justName.replace(/\.tsx?$/, ".errors.txt"), (): string => { - if (result.errors.length === 0) return null; - return getErrorBaseline(toBeCompiled, otherFiles, result); - }); + Harness.Compiler.doErrorBaseline(justName, toBeCompiled.concat(otherFiles), result.errors); } }); @@ -168,8 +155,10 @@ class CompilerBaselineRunner extends RunnerBase { Harness.Baseline.runBaseline(justName.replace(/\.tsx?$/, ".sourcemap.txt"), () => { const record = result.getSourceMapRecord(); if (options.noEmitOnError && result.errors.length !== 0 && record === undefined) { - // Because of the noEmitOnError option no files are created. We need to return null because baselining isn"t required. + // Because of the noEmitOnError option no files are created. We need to return null because baselining isn't required. + /* tslint:disable:no-null-keyword */ return null; + /* tslint:enable:no-null-keyword */ } return record; }); @@ -178,87 +167,12 @@ class CompilerBaselineRunner extends RunnerBase { it("Correct JS output for " + fileName, () => { if (hasNonDtsFiles && this.emit) { - if (!options.noEmit && result.files.length === 0 && result.errors.length === 0) { - throw new Error("Expected at least one js file to be emitted or at least one error to be created."); - } - - // check js output - Harness.Baseline.runBaseline(justName.replace(/\.tsx?/, ".js"), () => { - let tsCode = ""; - const tsSources = otherFiles.concat(toBeCompiled); - if (tsSources.length > 1) { - tsCode += "//// [" + fileName + "] ////\r\n\r\n"; - } - for (let i = 0; i < tsSources.length; i++) { - tsCode += "//// [" + Harness.Path.getFileName(tsSources[i].unitName) + "]\r\n"; - tsCode += tsSources[i].content + (i < (tsSources.length - 1) ? "\r\n" : ""); - } - - let jsCode = ""; - for (let i = 0; i < result.files.length; i++) { - jsCode += "//// [" + Harness.Path.getFileName(result.files[i].fileName) + "]\r\n"; - jsCode += getByteOrderMarkText(result.files[i]); - jsCode += result.files[i].code; - } - - if (result.declFilesCode.length > 0) { - jsCode += "\r\n\r\n"; - for (let i = 0; i < result.declFilesCode.length; i++) { - jsCode += "//// [" + Harness.Path.getFileName(result.declFilesCode[i].fileName) + "]\r\n"; - jsCode += getByteOrderMarkText(result.declFilesCode[i]); - jsCode += result.declFilesCode[i].code; - } - } - - const declFileCompilationResult = - Harness.Compiler.compileDeclarationFiles( - toBeCompiled, otherFiles, result, harnessSettings, options, /*currentDirectory*/ undefined); - - if (declFileCompilationResult && declFileCompilationResult.declResult.errors.length) { - jsCode += "\r\n\r\n//// [DtsFileErrors]\r\n"; - jsCode += "\r\n\r\n"; - jsCode += getErrorBaseline(declFileCompilationResult.declInputFiles, declFileCompilationResult.declOtherFiles, declFileCompilationResult.declResult); - } - - if (jsCode.length > 0) { - return tsCode + "\r\n\r\n" + jsCode; - } - else { - return null; - } - }); + Harness.Compiler.doJsEmitBaseline(justName, fileName, options, result, toBeCompiled, otherFiles, harnessSettings); } }); it("Correct Sourcemap output for " + fileName, () => { - if (options.inlineSourceMap) { - if (result.sourceMaps.length > 0) { - throw new Error("No sourcemap files should be generated if inlineSourceMaps was set."); - } - return null; - } - else if (options.sourceMap) { - if (result.sourceMaps.length !== result.files.length) { - throw new Error("Number of sourcemap files should be same as js files."); - } - - Harness.Baseline.runBaseline(justName.replace(/\.tsx?/, ".js.map"), () => { - if (options.noEmitOnError && result.errors.length !== 0 && result.sourceMaps.length === 0) { - // We need to return null here or the runBaseLine will actually create a empty file. - // Baselining isn't required here because there is no output. - return null; - } - - let sourceMapCode = ""; - for (let i = 0; i < result.sourceMaps.length; i++) { - sourceMapCode += "//// [" + Harness.Path.getFileName(result.sourceMaps[i].fileName) + "]\r\n"; - sourceMapCode += getByteOrderMarkText(result.sourceMaps[i]); - sourceMapCode += result.sourceMaps[i].code; - } - - return sourceMapCode; - }); - } + Harness.Compiler.doSourcemapBaseline(justName, options, result); }); it("Correct type/symbol baselines for " + fileName, () => { @@ -266,129 +180,7 @@ class CompilerBaselineRunner extends RunnerBase { return; } - // NEWTODO: Type baselines - if (result.errors.length !== 0) { - return; - } - - // The full walker simulates the types that you would get from doing a full - // compile. The pull walker simulates the types you get when you just do - // a type query for a random node (like how the LS would do it). Most of the - // time, these will be the same. However, occasionally, they can be different. - // Specifically, when the compiler internally depends on symbol IDs to order - // things, then we may see different results because symbols can be created in a - // different order with 'pull' operations, and thus can produce slightly differing - // output. - // - // For example, with a full type check, we may see a type displayed as: number | string - // But with a pull type check, we may see it as: string | number - // - // These types are equivalent, but depend on what order the compiler observed - // certain parts of the program. - - const program = result.program; - const allFiles = toBeCompiled.concat(otherFiles).filter(file => !!program.getSourceFile(file.unitName)); - - const fullWalker = new TypeWriterWalker(program, /*fullTypeCheck*/ true); - - const fullResults = ts.createMap(); - const pullResults = ts.createMap(); - - for (const sourceFile of allFiles) { - fullResults[sourceFile.unitName] = fullWalker.getTypeAndSymbols(sourceFile.unitName); - pullResults[sourceFile.unitName] = fullWalker.getTypeAndSymbols(sourceFile.unitName); - } - - // Produce baselines. The first gives the types for all expressions. - // The second gives symbols for all identifiers. - let e1: Error, e2: Error; - try { - checkBaseLines(/*isSymbolBaseLine*/ false); - } - catch (e) { - e1 = e; - } - - try { - checkBaseLines(/*isSymbolBaseLine*/ true); - } - catch (e) { - e2 = e; - } - - if (e1 || e2) { - throw e1 || e2; - } - - return; - - function checkBaseLines(isSymbolBaseLine: boolean) { - const fullBaseLine = generateBaseLine(fullResults, isSymbolBaseLine); - const pullBaseLine = generateBaseLine(pullResults, isSymbolBaseLine); - - const fullExtension = isSymbolBaseLine ? ".symbols" : ".types"; - const pullExtension = isSymbolBaseLine ? ".symbols.pull" : ".types.pull"; - - if (fullBaseLine !== pullBaseLine) { - Harness.Baseline.runBaseline(justName.replace(/\.tsx?/, fullExtension), () => fullBaseLine); - Harness.Baseline.runBaseline(justName.replace(/\.tsx?/, pullExtension), () => pullBaseLine); - } - else { - Harness.Baseline.runBaseline(justName.replace(/\.tsx?/, fullExtension), () => fullBaseLine); - } - } - - function generateBaseLine(typeWriterResults: ts.Map, isSymbolBaseline: boolean): string { - const typeLines: string[] = []; - const typeMap: { [fileName: string]: { [lineNum: number]: string[]; } } = {}; - - allFiles.forEach(file => { - const codeLines = file.content.split("\n"); - typeWriterResults[file.unitName].forEach(result => { - if (isSymbolBaseline && !result.symbol) { - return; - } - - const typeOrSymbolString = isSymbolBaseline ? result.symbol : result.type; - const formattedLine = result.sourceText.replace(/\r?\n/g, "") + " : " + typeOrSymbolString; - if (!typeMap[file.unitName]) { - typeMap[file.unitName] = {}; - } - - let typeInfo = [formattedLine]; - const existingTypeInfo = typeMap[file.unitName][result.line]; - if (existingTypeInfo) { - typeInfo = existingTypeInfo.concat(typeInfo); - } - typeMap[file.unitName][result.line] = typeInfo; - }); - - typeLines.push("=== " + file.unitName + " ===\r\n"); - for (let i = 0; i < codeLines.length; i++) { - const currentCodeLine = codeLines[i]; - typeLines.push(currentCodeLine + "\r\n"); - if (typeMap[file.unitName]) { - const typeInfo = typeMap[file.unitName][i]; - if (typeInfo) { - typeInfo.forEach(ty => { - typeLines.push(">" + ty + "\r\n"); - }); - if (i + 1 < codeLines.length && (codeLines[i + 1].match(/^\s*[{|}]\s*$/) || codeLines[i + 1].trim() === "")) { - } - else { - typeLines.push("\r\n"); - } - } - } - else { - typeLines.push("No type information for this code."); - } - } - }); - - return typeLines.join(""); - } - + Harness.Compiler.doTypeAndSymbolBaseline(justName, result, toBeCompiled.concat(otherFiles).filter(file => !!result.program.getSourceFile(file.unitName))); }); }); } diff --git a/src/harness/harness.ts b/src/harness/harness.ts index 3375b8e47e7dc..14cb3147c323d 100644 --- a/src/harness/harness.ts +++ b/src/harness/harness.ts @@ -1328,6 +1328,220 @@ namespace Harness { Harness.IO.newLine() + Harness.IO.newLine() + outputLines.join("\r\n"); } + export function doErrorBaseline(baselinePath: string, inputFiles: TestFile[], errors: ts.Diagnostic[]) { + Harness.Baseline.runBaseline(baselinePath.replace(/\.tsx?$/, ".errors.txt"), (): string => { + if (errors.length === 0) { + /* tslint:disable:no-null-keyword */ + return null; + /* tslint:enable:no-null-keyword */ + } + return getErrorBaseline(inputFiles, errors); + }); + } + + export function doTypeAndSymbolBaseline(baselinePath: string, result: CompilerResult, allFiles: {unitName: string, content: string}[]) { + if (result.errors.length !== 0) { + return; + } + // The full walker simulates the types that you would get from doing a full + // compile. The pull walker simulates the types you get when you just do + // a type query for a random node (like how the LS would do it). Most of the + // time, these will be the same. However, occasionally, they can be different. + // Specifically, when the compiler internally depends on symbol IDs to order + // things, then we may see different results because symbols can be created in a + // different order with 'pull' operations, and thus can produce slightly differing + // output. + // + // For example, with a full type check, we may see a type displayed as: number | string + // But with a pull type check, we may see it as: string | number + // + // These types are equivalent, but depend on what order the compiler observed + // certain parts of the program. + + const program = result.program; + + const fullWalker = new TypeWriterWalker(program, /*fullTypeCheck*/ true); + + const fullResults = ts.createMap(); + + for (const sourceFile of allFiles) { + fullResults[sourceFile.unitName] = fullWalker.getTypeAndSymbols(sourceFile.unitName); + } + + // Produce baselines. The first gives the types for all expressions. + // The second gives symbols for all identifiers. + let e1: Error, e2: Error; + try { + checkBaseLines(/*isSymbolBaseLine*/ false); + } + catch (e) { + e1 = e; + } + + try { + checkBaseLines(/*isSymbolBaseLine*/ true); + } + catch (e) { + e2 = e; + } + + if (e1 || e2) { + throw e1 || e2; + } + + return; + + function checkBaseLines(isSymbolBaseLine: boolean) { + const fullBaseLine = generateBaseLine(fullResults, isSymbolBaseLine); + + const fullExtension = isSymbolBaseLine ? ".symbols" : ".types"; + + Harness.Baseline.runBaseline(baselinePath.replace(/\.tsx?/, fullExtension), () => fullBaseLine); + } + + function generateBaseLine(typeWriterResults: ts.Map, isSymbolBaseline: boolean): string { + const typeLines: string[] = []; + const typeMap: { [fileName: string]: { [lineNum: number]: string[]; } } = {}; + + allFiles.forEach(file => { + const codeLines = file.content.split("\n"); + typeWriterResults[file.unitName].forEach(result => { + if (isSymbolBaseline && !result.symbol) { + return; + } + + const typeOrSymbolString = isSymbolBaseline ? result.symbol : result.type; + const formattedLine = result.sourceText.replace(/\r?\n/g, "") + " : " + typeOrSymbolString; + if (!typeMap[file.unitName]) { + typeMap[file.unitName] = {}; + } + + let typeInfo = [formattedLine]; + const existingTypeInfo = typeMap[file.unitName][result.line]; + if (existingTypeInfo) { + typeInfo = existingTypeInfo.concat(typeInfo); + } + typeMap[file.unitName][result.line] = typeInfo; + }); + + typeLines.push("=== " + file.unitName + " ===\r\n"); + for (let i = 0; i < codeLines.length; i++) { + const currentCodeLine = codeLines[i]; + typeLines.push(currentCodeLine + "\r\n"); + if (typeMap[file.unitName]) { + const typeInfo = typeMap[file.unitName][i]; + if (typeInfo) { + typeInfo.forEach(ty => { + typeLines.push(">" + ty + "\r\n"); + }); + if (i + 1 < codeLines.length && (codeLines[i + 1].match(/^\s*[{|}]\s*$/) || codeLines[i + 1].trim() === "")) { + } + else { + typeLines.push("\r\n"); + } + } + } + else { + typeLines.push("No type information for this code."); + } + } + }); + + return typeLines.join(""); + } + } + + function getByteOrderMarkText(file: Harness.Compiler.GeneratedFile): string { + return file.writeByteOrderMark ? "\u00EF\u00BB\u00BF" : ""; + } + + export function doSourcemapBaseline(baselinePath: string, options: ts.CompilerOptions, result: CompilerResult) { + if (options.inlineSourceMap) { + if (result.sourceMaps.length > 0) { + throw new Error("No sourcemap files should be generated if inlineSourceMaps was set."); + } + return; + } + else if (options.sourceMap) { + if (result.sourceMaps.length !== result.files.length) { + throw new Error("Number of sourcemap files should be same as js files."); + } + + Harness.Baseline.runBaseline(baselinePath.replace(/\.tsx?/, ".js.map"), () => { + if (options.noEmitOnError && result.errors.length !== 0 && result.sourceMaps.length === 0) { + // We need to return null here or the runBaseLine will actually create a empty file. + // Baselining isn't required here because there is no output. + /* tslint:disable:no-null-keyword */ + return null; + /* tslint:enable:no-null-keyword */ + } + + let sourceMapCode = ""; + for (let i = 0; i < result.sourceMaps.length; i++) { + sourceMapCode += "//// [" + Harness.Path.getFileName(result.sourceMaps[i].fileName) + "]\r\n"; + sourceMapCode += getByteOrderMarkText(result.sourceMaps[i]); + sourceMapCode += result.sourceMaps[i].code; + } + + return sourceMapCode; + }); + } + } + + export function doJsEmitBaseline(baselinePath: string, header: string, options: ts.CompilerOptions, result: CompilerResult, toBeCompiled: Harness.Compiler.TestFile[], otherFiles: Harness.Compiler.TestFile[], harnessSettings: Harness.TestCaseParser.CompilerSettings) { + if (!options.noEmit && result.files.length === 0 && result.errors.length === 0) { + throw new Error("Expected at least one js file to be emitted or at least one error to be created."); + } + + // check js output + Harness.Baseline.runBaseline(baselinePath.replace(/\.tsx?/, ".js"), () => { + let tsCode = ""; + const tsSources = otherFiles.concat(toBeCompiled); + if (tsSources.length > 1) { + tsCode += "//// [" + header + "] ////\r\n\r\n"; + } + for (let i = 0; i < tsSources.length; i++) { + tsCode += "//// [" + Harness.Path.getFileName(tsSources[i].unitName) + "]\r\n"; + tsCode += tsSources[i].content + (i < (tsSources.length - 1) ? "\r\n" : ""); + } + + let jsCode = ""; + for (let i = 0; i < result.files.length; i++) { + jsCode += "//// [" + Harness.Path.getFileName(result.files[i].fileName) + "]\r\n"; + jsCode += getByteOrderMarkText(result.files[i]); + jsCode += result.files[i].code; + } + + if (result.declFilesCode.length > 0) { + jsCode += "\r\n\r\n"; + for (let i = 0; i < result.declFilesCode.length; i++) { + jsCode += "//// [" + Harness.Path.getFileName(result.declFilesCode[i].fileName) + "]\r\n"; + jsCode += getByteOrderMarkText(result.declFilesCode[i]); + jsCode += result.declFilesCode[i].code; + } + } + + const declFileCompilationResult = + Harness.Compiler.compileDeclarationFiles( + toBeCompiled, otherFiles, result, harnessSettings, options, /*currentDirectory*/ undefined); + + if (declFileCompilationResult && declFileCompilationResult.declResult.errors.length) { + jsCode += "\r\n\r\n//// [DtsFileErrors]\r\n"; + jsCode += "\r\n\r\n"; + jsCode += Harness.Compiler.getErrorBaseline(declFileCompilationResult.declInputFiles.concat(declFileCompilationResult.declOtherFiles), declFileCompilationResult.declResult.errors); + } + + if (jsCode.length > 0) { + return tsCode + "\r\n\r\n" + jsCode; + } + else { + /* tslint:disable:no-null-keyword */ + return null; + /* tslint:enable:no-null-keyword */ + } + }); + } + export function collateOutputs(outputFiles: Harness.Compiler.GeneratedFile[]): string { // Collect, test, and sort the fileNames outputFiles.sort((a, b) => cleanName(a.fileName).localeCompare(cleanName(b.fileName))); diff --git a/src/harness/rwcRunner.ts b/src/harness/rwcRunner.ts index 17346016fbbd0..5720f9b541c46 100644 --- a/src/harness/rwcRunner.ts +++ b/src/harness/rwcRunner.ts @@ -222,7 +222,12 @@ namespace RWC { } }); - // TODO: Type baselines (need to refactor out from compilerRunner) + it("has the expected types", () => { + Harness.Compiler.doTypeAndSymbolBaseline(baseName, compilerResult, inputFiles + .concat(otherFiles) + .filter(file => !!compilerResult.program.getSourceFile(file.unitName)) + .filter(e => !Harness.isDefaultLibraryFile(e.unitName))); + }); }); } }