Move most harness globals into namespaces (#35530)

* Move most harness globals into namespaces

* Remove forward declaration from `createMapShim` and move all `Map` declarations into one file

* A small pile of more changes to get the harness transforming
This commit is contained in:
Wesley Wigham 2019-12-06 15:20:49 -08:00 committed by GitHub
parent 2b567b2699
commit a78342a160
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
53 changed files with 1399 additions and 1340 deletions

View File

@ -2,31 +2,8 @@
/* @internal */
namespace ts {
/**
* Returns the native Map implementation if it is available and compatible (i.e. supports iteration).
*/
export function tryGetNativeMap(): MapConstructor | undefined {
// Internet Explorer's Map doesn't support iteration, so don't use it.
// Natives
// NOTE: TS doesn't strictly allow in-line declares, but if we suppress the error, the declaration
// is still used for typechecking _and_ correctly elided, which is out goal, as this prevents us from
// needing to pollute an outer scope with a declaration of `Map` just to satisfy the checks in this function
//@ts-ignore
declare const Map: (new <T>() => Map<T>) | undefined;
// eslint-disable-next-line no-in-operator
return typeof Map !== "undefined" && "entries" in Map.prototype ? Map : undefined;
}
export const emptyArray: never[] = [] as never[];
export const Map: MapConstructor = tryGetNativeMap() || (() => {
// NOTE: createMapShim will be defined for typescriptServices.js but not for tsc.js, so we must test for it.
if (typeof createMapShim === "function") {
return createMapShim();
}
throw new Error("TypeScript requires an environment that provides a compatible native Map implementation.");
})();
/** Create a new map. */
export function createMap<T>(): Map<T> {
return new Map<T>();

View File

@ -46,6 +46,31 @@ namespace ts {
new <T>(): Map<T>;
}
/**
* Returns the native Map implementation if it is available and compatible (i.e. supports iteration).
*/
/* @internal */
export function tryGetNativeMap(): MapConstructor | undefined {
// Internet Explorer's Map doesn't support iteration, so don't use it.
// Natives
// NOTE: TS doesn't strictly allow in-line declares, but if we suppress the error, the declaration
// is still used for typechecking _and_ correctly elided, which is out goal, as this prevents us from
// needing to pollute an outer scope with a declaration of `Map` just to satisfy the checks in this function
//@ts-ignore
declare const Map: (new <T>() => Map<T>) | undefined;
// eslint-disable-next-line no-in-operator
return typeof Map !== "undefined" && "entries" in Map.prototype ? Map : undefined;
}
/* @internal */
export const Map: MapConstructor = tryGetNativeMap() || (() => {
// NOTE: createMapShim will be defined for typescriptServices.js but not for tsc.js, so we must test for it.
if (typeof createMapShim === "function") {
return createMapShim();
}
throw new Error("TypeScript requires an environment that provides a compatible native Map implementation.");
})();
/** ES6 Iterator type. */
export interface Iterator<T> {
next(): { value: T, done?: false } | { value: never, done: true };

View File

@ -39,7 +39,7 @@ namespace fakes {
public readFile(path: string) {
try {
const content = this.vfs.readFileSync(path, "utf8");
return content === undefined ? undefined : utils.removeByteOrderMark(content);
return content === undefined ? undefined : Utils.removeByteOrderMark(content);
}
catch {
return undefined;
@ -48,7 +48,7 @@ namespace fakes {
public writeFile(path: string, data: string, writeByteOrderMark?: boolean): void {
this.vfs.mkdirpSync(vpath.dirname(path));
this.vfs.writeFileSync(path, writeByteOrderMark ? utils.addUTF8ByteOrderMark(data) : data);
this.vfs.writeFileSync(path, writeByteOrderMark ? Utils.addUTF8ByteOrderMark(data) : data);
}
public deleteFile(path: string) {
@ -289,7 +289,7 @@ namespace fakes {
}
public writeFile(fileName: string, content: string, writeByteOrderMark: boolean) {
if (writeByteOrderMark) content = utils.addUTF8ByteOrderMark(content);
if (writeByteOrderMark) content = Utils.addUTF8ByteOrderMark(content);
this.sys.writeFile(fileName, content);
const document = new documents.TextDocument(fileName, content);

View File

@ -1,6 +1,4 @@
namespace FourSlash {
ts.disableIncrementalParsing = false;
import ArrayOrSingle = FourSlashInterface.ArrayOrSingle;
export const enum FourSlashTestType {

View File

@ -2,8 +2,9 @@
/* eslint-disable no-var */
// this will work in the browser via browserify
var _chai: typeof chai = require("chai");
var assert: typeof _chai.assert = _chai.assert;
declare var assert: typeof _chai.assert;
var _chai: typeof import("chai") = require("chai");
globalThis.assert = _chai.assert;
{
// chai's builtin `assert.isFalse` is featureful but slow - we don't use those features,
// so we'll just overwrite it as an alterative to migrating a bunch of code off of chai
@ -27,4 +28,7 @@ var assert: typeof _chai.assert = _chai.assert;
}
};
}
/* eslint-enable no-var */
/* eslint-enable no-var */
// empty ts namespace so this file is included in the `ts.ts` namespace file generated by the module swapover
// This way, everything that ends up importing `ts` downstream also imports this file and picks up its augmentation
namespace ts {}

View File

@ -29,6 +29,9 @@ namespace Harness {
}
export let IO: IO;
export function setHarnessIO(io: IO) {
IO = io;
}
// harness always uses one kind of new line
// But note that `parseTestData` in `fourslash.ts` uses "\n"
@ -185,6 +188,9 @@ namespace Harness {
export let userSpecifiedRoot = "";
export let lightMode = false;
/* eslint-enable prefer-const */
export function setLightMode(flag: boolean) {
lightMode = flag;
}
/** Functionality for compiling TypeScript code */
export namespace Compiler {
@ -467,7 +473,7 @@ namespace Harness {
else if (vpath.isTypeScript(file.unitName) || (vpath.isJavaScript(file.unitName) && options.allowJs)) {
const declFile = findResultCodeFile(file.unitName);
if (declFile && !findUnit(declFile.file, declInputFiles) && !findUnit(declFile.file, declOtherFiles)) {
dtsFiles.push({ unitName: declFile.file, content: utils.removeByteOrderMark(declFile.text) });
dtsFiles.push({ unitName: declFile.file, content: Utils.removeByteOrderMark(declFile.text) });
}
}
}
@ -557,7 +563,7 @@ namespace Harness {
function outputErrorText(error: ts.Diagnostic) {
const message = ts.flattenDiagnosticMessageText(error.messageText, IO.newLine());
const errLines = utils.removeTestPathPrefixes(message)
const errLines = Utils.removeTestPathPrefixes(message)
.split("\n")
.map(s => s.length > 0 && s.charAt(s.length - 1) === "\r" ? s.substr(0, s.length - 1) : s)
.filter(s => s.length > 0)
@ -580,7 +586,7 @@ namespace Harness {
}
}
yield [diagnosticSummaryMarker, utils.removeTestPathPrefixes(minimalDiagnosticsToString(diagnostics, options && options.pretty)) + IO.newLine() + IO.newLine(), diagnostics.length];
yield [diagnosticSummaryMarker, Utils.removeTestPathPrefixes(minimalDiagnosticsToString(diagnostics, options && options.pretty)) + IO.newLine() + IO.newLine(), diagnostics.length];
// Report global errors
const globalErrors = diagnostics.filter(err => !err.file);
@ -595,7 +601,7 @@ namespace Harness {
// Filter down to the errors in the file
const fileErrors = diagnostics.filter((e): e is ts.DiagnosticWithLocation => {
const errFn = e.file;
return !!errFn && ts.comparePaths(utils.removeTestPathPrefixes(errFn.fileName), utils.removeTestPathPrefixes(inputFile.unitName), options && options.currentDirectory || "", !(options && options.caseSensitive)) === ts.Comparison.EqualTo;
return !!errFn && ts.comparePaths(Utils.removeTestPathPrefixes(errFn.fileName), Utils.removeTestPathPrefixes(inputFile.unitName), options && options.currentDirectory || "", !(options && options.caseSensitive)) === ts.Comparison.EqualTo;
});
@ -812,7 +818,7 @@ namespace Harness {
}
typeLines += "\r\n";
}
yield [checkDuplicatedFileName(unitName, dupeCase), utils.removeTestPathPrefixes(typeLines)];
yield [checkDuplicatedFileName(unitName, dupeCase), Utils.removeTestPathPrefixes(typeLines)];
}
}
}
@ -901,8 +907,8 @@ namespace Harness {
}
function fileOutput(file: documents.TextDocument, harnessSettings: TestCaseParser.CompilerSettings): string {
const fileName = harnessSettings.fullEmitPaths ? utils.removeTestPathPrefixes(file.file) : ts.getBaseFileName(file.file);
return "//// [" + fileName + "]\r\n" + utils.removeTestPathPrefixes(file.text);
const fileName = harnessSettings.fullEmitPaths ? Utils.removeTestPathPrefixes(file.file) : ts.getBaseFileName(file.file);
return "//// [" + fileName + "]\r\n" + Utils.removeTestPathPrefixes(file.text);
}
export function collateOutputs(outputFiles: readonly documents.TextDocument[]): string {
@ -928,7 +934,7 @@ namespace Harness {
const dupeCase = ts.createMap<number>();
// Yield them
for (const outputFile of files) {
yield [checkDuplicatedFileName(outputFile.file, dupeCase), "/*====== " + outputFile.file + " ======*/\r\n" + utils.removeByteOrderMark(outputFile.text)];
yield [checkDuplicatedFileName(outputFile.file, dupeCase), "/*====== " + outputFile.file + " ======*/\r\n" + Utils.removeByteOrderMark(outputFile.text)];
}
function cleanName(fn: string) {

View File

@ -1,86 +1,86 @@
interface FileInformation {
contents?: string;
contentsPath?: string;
codepage: number;
bom?: string;
}
interface FindFileResult {
}
interface IoLogFile {
path: string;
codepage: number;
result?: FileInformation;
}
interface IoLog {
timestamp: string;
arguments: string[];
executingPath: string;
currentDirectory: string;
useCustomLibraryFile?: boolean;
filesRead: IoLogFile[];
filesWritten: {
path: string;
contents?: string;
contentsPath?: string;
bom: boolean;
}[];
filesDeleted: string[];
filesAppended: {
path: string;
contents?: string;
contentsPath?: string;
}[];
fileExists: {
path: string;
result?: boolean;
}[];
filesFound: {
path: string;
pattern: string;
result?: FindFileResult;
}[];
dirs: {
path: string;
re: string;
re_m: boolean;
re_g: boolean;
re_i: boolean;
opts: { recursive?: boolean; };
result?: string[];
}[];
dirExists: {
path: string;
result?: boolean;
}[];
dirsCreated: string[];
pathsResolved: {
path: string;
result?: string;
}[];
directoriesRead: {
path: string,
extensions: readonly string[] | undefined,
exclude: readonly string[] | undefined,
include: readonly string[] | undefined,
depth: number | undefined,
result: readonly string[],
}[];
useCaseSensitiveFileNames?: boolean;
}
interface PlaybackControl {
startReplayFromFile(logFileName: string): void;
startReplayFromString(logContents: string): void;
startReplayFromData(log: IoLog): void;
endReplay(): void;
startRecord(logFileName: string): void;
endRecord(): void;
}
namespace Playback {
interface FileInformation {
contents?: string;
contentsPath?: string;
codepage: number;
bom?: string;
}
interface FindFileResult {
}
interface IoLogFile {
path: string;
codepage: number;
result?: FileInformation;
}
export interface IoLog {
timestamp: string;
arguments: string[];
executingPath: string;
currentDirectory: string;
useCustomLibraryFile?: boolean;
filesRead: IoLogFile[];
filesWritten: {
path: string;
contents?: string;
contentsPath?: string;
bom: boolean;
}[];
filesDeleted: string[];
filesAppended: {
path: string;
contents?: string;
contentsPath?: string;
}[];
fileExists: {
path: string;
result?: boolean;
}[];
filesFound: {
path: string;
pattern: string;
result?: FindFileResult;
}[];
dirs: {
path: string;
re: string;
re_m: boolean;
re_g: boolean;
re_i: boolean;
opts: { recursive?: boolean; };
result?: string[];
}[];
dirExists: {
path: string;
result?: boolean;
}[];
dirsCreated: string[];
pathsResolved: {
path: string;
result?: string;
}[];
directoriesRead: {
path: string,
extensions: readonly string[] | undefined,
exclude: readonly string[] | undefined,
include: readonly string[] | undefined,
depth: number | undefined,
result: readonly string[],
}[];
useCaseSensitiveFileNames?: boolean;
}
interface PlaybackControl {
startReplayFromFile(logFileName: string): void;
startReplayFromString(logContents: string): void;
startReplayFromData(log: IoLog): void;
endReplay(): void;
startRecord(logFileName: string): void;
endRecord(): void;
}
let recordLog: IoLog | undefined;
let replayLog: IoLog | undefined;
let replayFilesRead: ts.Map<IoLogFile> | undefined;

View File

@ -1,53 +1,63 @@
type TestRunnerKind = CompilerTestKind | FourslashTestKind | "project" | "rwc" | "test262" | "user" | "dt" | "docker";
type CompilerTestKind = "conformance" | "compiler";
type FourslashTestKind = "fourslash" | "fourslash-shims" | "fourslash-shims-pp" | "fourslash-server";
namespace Harness {
export type TestRunnerKind = CompilerTestKind | FourslashTestKind | "project" | "rwc" | "test262" | "user" | "dt" | "docker";
export type CompilerTestKind = "conformance" | "compiler";
export type FourslashTestKind = "fourslash" | "fourslash-shims" | "fourslash-shims-pp" | "fourslash-server";
/* eslint-disable prefer-const */
let shards = 1;
let shardId = 1;
/* eslint-enable prefer-const */
/* eslint-disable prefer-const */
export let shards = 1;
export let shardId = 1;
/* eslint-enable prefer-const */
abstract class RunnerBase {
// contains the tests to run
public tests: (string | Harness.FileBasedTest)[] = [];
/** Add a source file to the runner's list of tests that need to be initialized with initializeTests */
public addTest(fileName: string) {
this.tests.push(fileName);
// The following have setters as while they're read here in the harness, they're only set in the runner
export function setShards(count: number) {
shards = count;
}
export function setShardId(id: number) {
shardId = id;
}
public enumerateFiles(folder: string, regex?: RegExp, options?: { recursive: boolean }): string[] {
return ts.map(Harness.IO.listFiles(Harness.userSpecifiedRoot + folder, regex, { recursive: (options ? options.recursive : false) }), ts.normalizeSlashes);
}
export abstract class RunnerBase {
// contains the tests to run
public tests: (string | FileBasedTest)[] = [];
abstract kind(): TestRunnerKind;
abstract enumerateTestFiles(): (string | Harness.FileBasedTest)[];
getTestFiles(): ReturnType<this["enumerateTestFiles"]> {
const all = this.enumerateTestFiles();
if (shards === 1) {
return all as ReturnType<this["enumerateTestFiles"]>;
/** Add a source file to the runner's list of tests that need to be initialized with initializeTests */
public addTest(fileName: string) {
this.tests.push(fileName);
}
public enumerateFiles(folder: string, regex?: RegExp, options?: { recursive: boolean }): string[] {
return ts.map(IO.listFiles(userSpecifiedRoot + folder, regex, { recursive: (options ? options.recursive : false) }), ts.normalizeSlashes);
}
abstract kind(): TestRunnerKind;
abstract enumerateTestFiles(): (string | FileBasedTest)[];
getTestFiles(): ReturnType<this["enumerateTestFiles"]> {
const all = this.enumerateTestFiles();
if (shards === 1) {
return all as ReturnType<this["enumerateTestFiles"]>;
}
return all.filter((_val, idx) => idx % shards === (shardId - 1)) as ReturnType<this["enumerateTestFiles"]>;
}
/** The working directory where tests are found. Needed for batch testing where the input path will differ from the output path inside baselines */
public workingDirectory = "";
/** Setup the runner's tests so that they are ready to be executed by the harness
* The first test should be a describe/it block that sets up the harness's compiler instance appropriately
*/
public abstract initializeTests(): void;
/** Replaces instances of full paths with fileNames only */
static removeFullPaths(path: string) {
// If its a full path (starts with "C:" or "/") replace with just the filename
let fixedPath = /^(\w:|\/)/.test(path) ? ts.getBaseFileName(path) : path;
// when running in the browser the 'full path' is the host name, shows up in error baselines
const localHost = /http:\/localhost:\d+/g;
fixedPath = fixedPath.replace(localHost, "");
return fixedPath;
}
return all.filter((_val, idx) => idx % shards === (shardId - 1)) as ReturnType<this["enumerateTestFiles"]>;
}
/** The working directory where tests are found. Needed for batch testing where the input path will differ from the output path inside baselines */
public workingDirectory = "";
/** Setup the runner's tests so that they are ready to be executed by the harness
* The first test should be a describe/it block that sets up the harness's compiler instance appropriately
*/
public abstract initializeTests(): void;
/** Replaces instances of full paths with fileNames only */
static removeFullPaths(path: string) {
// If its a full path (starts with "C:" or "/") replace with just the filename
let fixedPath = /^(\w:|\/)/.test(path) ? ts.getBaseFileName(path) : path;
// when running in the browser the 'full path' is the host name, shows up in error baselines
const localHost = /http:\/localhost:\d+/g;
fixedPath = fixedPath.replace(localHost, "");
return fixedPath;
}
}
}

View File

@ -157,7 +157,7 @@ namespace Harness.SourceMapRecorder {
const startPos = lineMap[line];
const endPos = lineMap[line + 1];
const text = code.substring(startPos, endPos);
return line === 0 ? utils.removeByteOrderMark(text) : text;
return line === 0 ? Utils.removeByteOrderMark(text) : text;
}
function writeJsFileLines(endJsLine: number) {

View File

@ -19,14 +19,14 @@
],
"files": [
"collections.ts",
"utils.ts",
"documents.ts",
"vpath.ts",
"vfs.ts",
"compiler.ts",
"evaluator.ts",
"fakes.ts",
"collectionsImpl.ts",
"util.ts",
"documentsUtil.ts",
"vpathUtil.ts",
"vfsUtil.ts",
"compilerImpl.ts",
"evaluatorImpl.ts",
"fakesHosts.ts",
"client.ts",
"runnerbase.ts",
@ -36,8 +36,8 @@
"harnessIO.ts",
"harnessLanguageService.ts",
"virtualFileSystemWithWatch.ts",
"fourslash.ts",
"fourslashInterface.ts",
"fourslashImpl.ts",
"fourslashInterfaceImpl.ts",
"typeWriter.ts",
"loggedIO.ts"
]

View File

@ -1,7 +1,7 @@
/**
* Common utilities
*/
namespace utils {
namespace Utils {
const testPathPrefixRegExp = /(?:(file:\/{3})|\/)\.(ts|lib|src)\//g;
export function removeTestPathPrefixes(text: string, retainTrailingDirectorySeparator?: boolean): string {
return text !== undefined ? text.replace(testPathPrefixRegExp, (_, scheme) => scheme || (retainTrailingDirectorySeparator ? "/" : "")) : undefined!; // TODO: GH#18217

View File

@ -5,7 +5,7 @@ declare namespace ts.server {
data: any;
}
type RequireResult = { module: {}, error: undefined } | { module: undefined, error: { stack?: string, message?: string } };
export type RequireResult = { module: {}, error: undefined } | { module: undefined, error: { stack?: string, message?: string } };
export interface ServerHost extends System {
watchFile(path: string, callback: FileWatcherCallback, pollingInterval?: number): FileWatcher;
watchDirectory(path: string, callback: DirectoryWatcherCallback, recursive?: boolean): FileWatcher;

View File

@ -1025,59 +1025,54 @@ namespace ts {
return sourceFile;
}
export let disableIncrementalParsing = false; // eslint-disable-line prefer-const
export function updateLanguageServiceSourceFile(sourceFile: SourceFile, scriptSnapshot: IScriptSnapshot, version: string, textChangeRange: TextChangeRange | undefined, aggressiveChecks?: boolean): SourceFile {
// If we were given a text change range, and our version or open-ness changed, then
// incrementally parse this file.
if (textChangeRange) {
if (version !== sourceFile.version) {
// Once incremental parsing is ready, then just call into this function.
if (!disableIncrementalParsing) {
let newText: string;
let newText: string;
// grab the fragment from the beginning of the original text to the beginning of the span
const prefix = textChangeRange.span.start !== 0
? sourceFile.text.substr(0, textChangeRange.span.start)
: "";
// grab the fragment from the beginning of the original text to the beginning of the span
const prefix = textChangeRange.span.start !== 0
? sourceFile.text.substr(0, textChangeRange.span.start)
: "";
// grab the fragment from the end of the span till the end of the original text
const suffix = textSpanEnd(textChangeRange.span) !== sourceFile.text.length
? sourceFile.text.substr(textSpanEnd(textChangeRange.span))
: "";
// grab the fragment from the end of the span till the end of the original text
const suffix = textSpanEnd(textChangeRange.span) !== sourceFile.text.length
? sourceFile.text.substr(textSpanEnd(textChangeRange.span))
: "";
if (textChangeRange.newLength === 0) {
// edit was a deletion - just combine prefix and suffix
newText = prefix && suffix ? prefix + suffix : prefix || suffix;
}
else {
// it was actual edit, fetch the fragment of new text that correspond to new span
const changedText = scriptSnapshot.getText(textChangeRange.span.start, textChangeRange.span.start + textChangeRange.newLength);
// combine prefix, changed text and suffix
newText = prefix && suffix
? prefix + changedText + suffix
: prefix
? (prefix + changedText)
: (changedText + suffix);
}
const newSourceFile = updateSourceFile(sourceFile, newText, textChangeRange, aggressiveChecks);
setSourceFileFields(newSourceFile, scriptSnapshot, version);
// after incremental parsing nameTable might not be up-to-date
// drop it so it can be lazily recreated later
newSourceFile.nameTable = undefined;
// dispose all resources held by old script snapshot
if (sourceFile !== newSourceFile && sourceFile.scriptSnapshot) {
if (sourceFile.scriptSnapshot.dispose) {
sourceFile.scriptSnapshot.dispose();
}
sourceFile.scriptSnapshot = undefined;
}
return newSourceFile;
if (textChangeRange.newLength === 0) {
// edit was a deletion - just combine prefix and suffix
newText = prefix && suffix ? prefix + suffix : prefix || suffix;
}
else {
// it was actual edit, fetch the fragment of new text that correspond to new span
const changedText = scriptSnapshot.getText(textChangeRange.span.start, textChangeRange.span.start + textChangeRange.newLength);
// combine prefix, changed text and suffix
newText = prefix && suffix
? prefix + changedText + suffix
: prefix
? (prefix + changedText)
: (changedText + suffix);
}
const newSourceFile = updateSourceFile(sourceFile, newText, textChangeRange, aggressiveChecks);
setSourceFileFields(newSourceFile, scriptSnapshot, version);
// after incremental parsing nameTable might not be up-to-date
// drop it so it can be lazily recreated later
newSourceFile.nameTable = undefined;
// dispose all resources held by old script snapshot
if (sourceFile !== newSourceFile && sourceFile.scriptSnapshot) {
if (sourceFile.scriptSnapshot.dispose) {
sourceFile.scriptSnapshot.dispose();
}
sourceFile.scriptSnapshot = undefined;
}
return newSourceFile;
}
}

View File

@ -6,7 +6,7 @@ interface PromiseConstructor {
all<T>(values: (T | PromiseLike<T>)[]): Promise<T[]>;
}
/* @internal */
declare let Promise: PromiseConstructor;
declare var Promise: PromiseConstructor; // eslint-disable-line no-var
/* @internal */
namespace ts {

View File

@ -1,13 +1,21 @@
/* @internal */
namespace ts {
// NOTE: Due to how the project-reference merging ends up working, `T` isn't considered referenced until `Map` merges with the definition
// in src/compiler/core.ts
// @ts-ignore
export interface Map<T> {
// full type defined in ~/src/compiler/core.ts
interface IteratorShim<T> {
next(): { value: T, done?: false } | { value: never, done: true };
}
export function createMapShim(): new <T>() => Map<T> {
interface MapShim<T> {
readonly size: number;
get(key: string): T | undefined;
set(key: string, value: T): this;
has(key: string): boolean;
delete(key: string): boolean;
clear(): void;
keys(): IteratorShim<string>;
values(): IteratorShim<T>;
entries(): IteratorShim<[string, T]>;
forEach(action: (value: T, key: string) => void): void;
}
export function createMapShim(): new <T>() => MapShim<T> {
/** Create a MapLike with good performance. */
function createDictionaryObject<T>(): Record<string, T> {
const map = Object.create(/*prototype*/ null); // eslint-disable-line no-null/no-null
@ -46,7 +54,7 @@ namespace ts {
this.selector = selector;
}
public next(): { value: U, done: false } | { value: never, done: true } {
public next(): { value: U, done?: false } | { value: never, done: true } {
// Navigate to the next entry.
while (this.currentEntry) {
const skipNext = !!this.currentEntry.skipNext;
@ -66,7 +74,7 @@ namespace ts {
}
}
return class <T> implements Map<T> {
return class <T> implements MapShim<T> {
private data = createDictionaryObject<MapEntry<T>>();
public size = 0;
@ -183,15 +191,15 @@ namespace ts {
this.lastEntry = firstEntry;
}
keys(): Iterator<string> {
keys(): IteratorShim<string> {
return new MapIterator(this.firstEntry, key => key);
}
values(): Iterator<T> {
values(): IteratorShim<T> {
return new MapIterator(this.firstEntry, (_key, value) => value);
}
entries(): Iterator<[string, T]> {
entries(): IteratorShim<[string, T]> {
return new MapIterator(this.firstEntry, (key, value) => [key, value] as [string, T]);
}

View File

@ -0,0 +1,2 @@
// empty ref to compiler so it can be referenced by unittests
namespace compiler {}

View File

@ -1,333 +1,335 @@
const enum CompilerTestType {
Conformance,
Regressions,
Test262
}
interface CompilerFileBasedTest extends Harness.FileBasedTest {
readonly content?: string;
}
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;
namespace Harness {
export const enum CompilerTestType {
Conformance,
Regressions,
Test262
}
public kind() {
return this.testSuiteName;
interface CompilerFileBasedTest extends FileBasedTest {
readonly content?: string;
}
public enumerateTestFiles() {
// see also: `enumerateTestFiles` in tests/webTestServer.ts
return this.enumerateFiles(this.basePath, /\.tsx?$/, { recursive: true }).map(CompilerTest.getConfigurations);
}
export class CompilerBaselineRunner extends RunnerBase {
private basePath = "tests/cases";
private testSuiteName: TestRunnerKind;
private emit: boolean;
public initializeTests() {
describe(this.testSuiteName + " tests", () => {
describe("Setup compiler for compiler baselines", () => {
this.parseOptions();
});
public options: string | undefined;
// 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 : Harness.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);
});
});
}
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 checkTestCodeOutput(fileName: string, test?: CompilerFileBasedTest) {
if (test && ts.some(test.configurations)) {
test.configurations.forEach(configuration => {
describe(`${this.testSuiteName} tests for ${fileName}${configuration ? ` (${Harness.getFileBasedTestConfigurationDescription(configuration)})` : ``}`, () => {
this.runSuite(fileName, test, configuration);
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);
});
});
}
else {
describe(`${this.testSuiteName} tests for ${fileName}`, () => {
this.runSuite(fileName, test);
});
}
}
private runSuite(fileName: string, test?: CompilerFileBasedTest, configuration?: Harness.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 = Harness.TestCaseParser.makeUnitsFromTest(test.content, test.file, rootDir);
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);
});
});
}
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}`, () => { if (this.emit) compilerTest.verifyJavaScriptOutput(); });
it(`Correct Sourcemap output for ${fileName}`, () => { compilerTest.verifySourceMapOutput(); });
it(`Correct type/symbol baselines for ${fileName}`, () => { compilerTest.verifyTypesAndSymbols(); });
after(() => { compilerTest = undefined!; });
}
else {
describe(`${this.testSuiteName} tests for ${fileName}`, () => {
this.runSuite(fileName, test);
});
}
}
private parseOptions() {
if (this.options && this.options.length > 0) {
this.emit = false;
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}`, () => { if (this.emit) compilerTest.verifyJavaScriptOutput(); });
it(`Correct Sourcemap output for ${fileName}`, () => { compilerTest.verifySourceMapOutput(); });
it(`Correct type/symbol baselines for ${fileName}`, () => { compilerTest.verifyTypesAndSymbols(); });
after(() => { compilerTest = undefined!; });
}
const opts = this.options.split(",");
for (const opt of opts) {
switch (opt) {
case "emit":
this.emit = true;
break;
default:
throw new Error("unsupported flag");
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",
"target",
"jsx",
"removeComments",
"importHelpers",
"importHelpers",
"downlevelIteration",
"isolatedModules",
"strict",
"noImplicitAny",
"strictNullChecks",
"strictFunctionTypes",
"strictBindCallApply",
"strictPropertyInitialization",
"noImplicitThis",
"alwaysStrict",
"allowSyntheticDefaultImports",
"esModuleInterop",
"emitDecoratorMetadata",
"skipDefaultLibCheck",
"preserveConstEnums",
"skipLibCheck",
];
private fileName: string;
private justName: string;
private configuredName: string;
private lastUnit: Harness.TestCaseParser.TestUnitData;
private harnessSettings: Harness.TestCaseParser.CompilerSettings;
private hasNonDtsFiles: boolean;
private result: compiler.CompilationResult;
private options: ts.CompilerOptions;
private tsConfigFiles: Harness.Compiler.TestFile[];
// equivalent to the files that will be passed on the command line
private toBeCompiled: Harness.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: Harness.Compiler.TestFile[];
class CompilerTest {
private static varyBy: readonly string[] = [
"module",
"target",
"jsx",
"removeComments",
"importHelpers",
"importHelpers",
"downlevelIteration",
"isolatedModules",
"strict",
"noImplicitAny",
"strictNullChecks",
"strictFunctionTypes",
"strictBindCallApply",
"strictPropertyInitialization",
"noImplicitThis",
"alwaysStrict",
"allowSyntheticDefaultImports",
"esModuleInterop",
"emitDecoratorMetadata",
"skipDefaultLibCheck",
"preserveConstEnums",
"skipLibCheck",
];
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?: Harness.TestCaseParser.TestCaseContent, configurationOverrides?: Harness.TestCaseParser.CompilerSettings) {
this.fileName = fileName;
this.justName = vpath.basename(fileName);
this.configuredName = this.justName;
if (configurationOverrides) {
let configuredName = "";
const keys = Object
.keys(configurationOverrides)
.map(k => k.toLowerCase())
.sort();
for (const key of keys) {
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)
.map(k => k.toLowerCase())
.sort();
for (const key of keys) {
if (configuredName) {
configuredName += ",";
}
configuredName += `${key}=${configurationOverrides[key].toLowerCase()}`;
}
if (configuredName) {
configuredName += ",";
const extname = vpath.extname(this.justName);
const basename = vpath.basename(this.justName, extname, /*ignoreCase*/ true);
this.configuredName = `${basename}(${configuredName})${extname}`;
}
configuredName += `${key}=${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);
}
}
const rootDir = fileName.indexOf("conformance") === -1 ? "tests/cases/compiler/" : ts.getDirectoryPath(fileName) + "/";
if (testCaseContent === undefined) {
testCaseContent = Harness.TestCaseParser.makeUnitsFromTest(Harness.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);
if (configurationOverrides) {
testCaseContent = { ...testCaseContent, settings: { ...testCaseContent.settings, ...configurationOverrides } };
}
}
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 = [];
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`);
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));
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);
}
});
}
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.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 = [];
this.result = Harness.Compiler.compileFiles(
this.toBeCompiled,
this.otherFiles,
this.harnessSettings,
/*options*/ tsConfigOptions,
/*currentDirectory*/ this.harnessSettings.currentDirectory,
testCaseContent.symlinks
);
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);
});
}
this.options = this.result.options;
}
if (tsConfigOptions && tsConfigOptions.configFilePath !== undefined) {
tsConfigOptions.configFilePath = ts.combinePaths(rootDir, tsConfigOptions.configFilePath);
tsConfigOptions.configFile!.fileName = tsConfigOptions.configFilePath;
}
public static getConfigurations(file: string): CompilerFileBasedTest {
// also see `parseCompilerTestConfigurations` in tests/webTestServer.ts
const content = Harness.IO.readFile(file)!;
const settings = Harness.TestCaseParser.extractCompilerSettings(content);
const configurations = Harness.getFileBasedTestConfigurations(settings, CompilerTest.varyBy);
return { file, configurations, content };
}
public verifyDiagnostics() {
// check errors
Harness.Compiler.doErrorBaseline(
this.configuredName,
this.tsConfigFiles.concat(this.toBeCompiled, this.otherFiles),
this.result.diagnostics,
!!this.options.pretty);
}
public verifyModuleResolution() {
if (this.options.traceResolution) {
Harness.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;
Harness.Baseline.runBaseline(this.configuredName.replace(/\.tsx?$/, ".sourcemap.txt"), baseline);
}
}
public verifyJavaScriptOutput() {
if (this.hasNonDtsFiles) {
Harness.Compiler.doJsEmitBaseline(
this.configuredName,
this.fileName,
this.options,
this.result,
this.tsConfigFiles,
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 verifySourceMapOutput() {
Harness.Compiler.doSourcemapBaseline(
this.configuredName,
this.options,
this.result,
this.harnessSettings);
}
public verifyTypesAndSymbols() {
if (this.fileName.indexOf("APISample") >= 0) {
return;
}
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)
);
}
const noTypesAndSymbols =
this.harnessSettings.noTypesAndSymbols &&
this.harnessSettings.noTypesAndSymbols.toLowerCase() === "true";
if (noTypesAndSymbols) {
return;
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;
}
Harness.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 createHarnessTestFile(lastUnit: TestCaseParser.TestUnitData, rootDir: string, unitName?: string): Compiler.TestFile {
return { unitName: unitName || this.makeUnitName(lastUnit.name, rootDir), content: lastUnit.content, fileOptions: lastUnit.fileOptions };
}
}
private makeUnitName(name: string, root: string) {
const path = ts.toPath(name, root, ts.identity);
const pathStart = ts.toPath(Harness.IO.getCurrentDirectory(), "", ts.identity);
return pathStart ? path.replace(pathStart, "/") : path;
}
private createHarnessTestFile(lastUnit: Harness.TestCaseParser.TestUnitData, rootDir: string, unitName?: string): Harness.Compiler.TestFile {
return { unitName: unitName || this.makeUnitName(lastUnit.name, rootDir), content: lastUnit.content, fileOptions: lastUnit.fileOptions };
}
}
}

View File

@ -0,0 +1,2 @@
// empty ref to documents so it can be referenced by unittests
namespace documents {}

View File

@ -0,0 +1,2 @@
// empty ref to evaluator so it can be referenced by unittests
namespace evaluator {}

View File

@ -1,338 +1,340 @@
const fs: typeof import("fs") = require("fs");
const path: typeof import("path") = require("path");
const del: typeof import("del") = require("del");
namespace Harness {
const fs: typeof import("fs") = require("fs");
const path: typeof import("path") = require("path");
const del: typeof import("del") = require("del");
interface ExecResult {
stdout: Buffer;
stderr: Buffer;
status: number | null;
}
interface UserConfig {
types: string[];
cloneUrl: string;
path?: string;
}
abstract class ExternalCompileRunnerBase extends RunnerBase {
abstract testDir: string;
abstract report(result: ExecResult, cwd: string): string | null;
enumerateTestFiles() {
return Harness.IO.getDirectories(this.testDir);
interface ExecResult {
stdout: Buffer;
stderr: Buffer;
status: number | null;
}
/** Setup the runner's tests so that they are ready to be executed by the harness
* The first test should be a describe/it block that sets up the harness's compiler instance appropriately
*/
initializeTests(): void {
// Read in and evaluate the test list
const testList = this.tests && this.tests.length ? this.tests : this.getTestFiles();
// eslint-disable-next-line @typescript-eslint/no-this-alias
const cls = this;
describe(`${this.kind()} code samples`, function(this: Mocha.ISuiteCallbackContext) {
this.timeout(600_000); // 10 minutes
for (const test of testList) {
cls.runTest(typeof test === "string" ? test : test.file);
}
});
interface UserConfig {
types: string[];
cloneUrl: string;
path?: string;
}
private runTest(directoryName: string) {
// eslint-disable-next-line @typescript-eslint/no-this-alias
const cls = this;
const timeout = 600_000; // 10 minutes
describe(directoryName, function(this: Mocha.ISuiteCallbackContext) {
this.timeout(timeout);
const cp: typeof import("child_process") = require("child_process");
it("should build successfully", () => {
let cwd = path.join(Harness.IO.getWorkspaceRoot(), cls.testDir, directoryName);
const originalCwd = cwd;
const stdio = isWorker ? "pipe" : "inherit";
let types: string[] | undefined;
if (fs.existsSync(path.join(cwd, "test.json"))) {
const config = JSON.parse(fs.readFileSync(path.join(cwd, "test.json"), { encoding: "utf8" })) as UserConfig;
ts.Debug.assert(!!config.types, "Bad format from test.json: Types field must be present.");
ts.Debug.assert(!!config.cloneUrl, "Bad format from test.json: cloneUrl field must be present.");
const submoduleDir = path.join(cwd, directoryName);
if (!fs.existsSync(submoduleDir)) {
exec("git", ["clone", config.cloneUrl, directoryName], { cwd });
}
exec("git", ["reset", "HEAD", "--hard"], { cwd: submoduleDir });
exec("git", ["clean", "-f"], { cwd: submoduleDir });
exec("git", ["pull", "-f"], { cwd: submoduleDir });
abstract class ExternalCompileRunnerBase extends RunnerBase {
abstract testDir: string;
abstract report(result: ExecResult, cwd: string): string | null;
enumerateTestFiles() {
return IO.getDirectories(this.testDir);
}
/** Setup the runner's tests so that they are ready to be executed by the harness
* The first test should be a describe/it block that sets up the harness's compiler instance appropriately
*/
initializeTests(): void {
// Read in and evaluate the test list
const testList = this.tests && this.tests.length ? this.tests : this.getTestFiles();
types = config.types;
cwd = config.path ? path.join(cwd, config.path) : submoduleDir;
}
if (fs.existsSync(path.join(cwd, "package.json"))) {
if (fs.existsSync(path.join(cwd, "package-lock.json"))) {
fs.unlinkSync(path.join(cwd, "package-lock.json"));
}
if (fs.existsSync(path.join(cwd, "node_modules"))) {
del.sync(path.join(cwd, "node_modules"), { force: true });
}
exec("npm", ["i", "--ignore-scripts"], { cwd, timeout: timeout / 2 }); // NPM shouldn't take the entire timeout - if it takes a long time, it should be terminated and we should log the failure
}
const args = [path.join(Harness.IO.getWorkspaceRoot(), "built/local/tsc.js")];
if (types) {
args.push("--types", types.join(","));
// Also actually install those types (for, eg, the js projects which need node)
exec("npm", ["i", ...types.map(t => `@types/${t}`), "--no-save", "--ignore-scripts"], { cwd: originalCwd, timeout: timeout / 2 }); // NPM shouldn't take the entire timeout - if it takes a long time, it should be terminated and we should log the failure
}
args.push("--noEmit");
Harness.Baseline.runBaseline(`${cls.kind()}/${directoryName}.log`, cls.report(cp.spawnSync(`node`, args, { cwd, timeout, shell: true }), cwd));
function exec(command: string, args: string[], options: { cwd: string, timeout?: number }): void {
const res = cp.spawnSync(command, args, { timeout, shell: true, stdio, ...options });
if (res.status !== 0) {
throw new Error(`${command} ${args.join(" ")} for ${directoryName} failed: ${res.stderr && res.stderr.toString()}`);
}
// eslint-disable-next-line @typescript-eslint/no-this-alias
const cls = this;
describe(`${this.kind()} code samples`, function(this: Mocha.ISuiteCallbackContext) {
this.timeout(600_000); // 10 minutes
for (const test of testList) {
cls.runTest(typeof test === "string" ? test : test.file);
}
});
});
}
}
}
private runTest(directoryName: string) {
// eslint-disable-next-line @typescript-eslint/no-this-alias
const cls = this;
const timeout = 600_000; // 10 minutes
describe(directoryName, function(this: Mocha.ISuiteCallbackContext) {
this.timeout(timeout);
const cp: typeof import("child_process") = require("child_process");
class UserCodeRunner extends ExternalCompileRunnerBase {
readonly testDir = "tests/cases/user/";
kind(): TestRunnerKind {
return "user";
}
report(result: ExecResult) {
// eslint-disable-next-line no-null/no-null
return result.status === 0 && !result.stdout.length && !result.stderr.length ? null : `Exit Code: ${result.status}
Standard output:
${sortErrors(stripAbsoluteImportPaths(result.stdout.toString().replace(/\r\n/g, "\n")))}
it("should build successfully", () => {
let cwd = path.join(IO.getWorkspaceRoot(), cls.testDir, directoryName);
const originalCwd = cwd;
const stdio = isWorker ? "pipe" : "inherit";
let types: string[] | undefined;
if (fs.existsSync(path.join(cwd, "test.json"))) {
const config = JSON.parse(fs.readFileSync(path.join(cwd, "test.json"), { encoding: "utf8" })) as UserConfig;
ts.Debug.assert(!!config.types, "Bad format from test.json: Types field must be present.");
ts.Debug.assert(!!config.cloneUrl, "Bad format from test.json: cloneUrl field must be present.");
const submoduleDir = path.join(cwd, directoryName);
if (!fs.existsSync(submoduleDir)) {
exec("git", ["clone", config.cloneUrl, directoryName], { cwd });
}
exec("git", ["reset", "HEAD", "--hard"], { cwd: submoduleDir });
exec("git", ["clean", "-f"], { cwd: submoduleDir });
exec("git", ["pull", "-f"], { cwd: submoduleDir });
types = config.types;
Standard error:
${stripAbsoluteImportPaths(result.stderr.toString().replace(/\r\n/g, "\n"))}`;
}
}
cwd = config.path ? path.join(cwd, config.path) : submoduleDir;
}
if (fs.existsSync(path.join(cwd, "package.json"))) {
if (fs.existsSync(path.join(cwd, "package-lock.json"))) {
fs.unlinkSync(path.join(cwd, "package-lock.json"));
}
if (fs.existsSync(path.join(cwd, "node_modules"))) {
del.sync(path.join(cwd, "node_modules"), { force: true });
}
exec("npm", ["i", "--ignore-scripts"], { cwd, timeout: timeout / 2 }); // NPM shouldn't take the entire timeout - if it takes a long time, it should be terminated and we should log the failure
}
const args = [path.join(IO.getWorkspaceRoot(), "built/local/tsc.js")];
if (types) {
args.push("--types", types.join(","));
// Also actually install those types (for, eg, the js projects which need node)
exec("npm", ["i", ...types.map(t => `@types/${t}`), "--no-save", "--ignore-scripts"], { cwd: originalCwd, timeout: timeout / 2 }); // NPM shouldn't take the entire timeout - if it takes a long time, it should be terminated and we should log the failure
}
args.push("--noEmit");
Baseline.runBaseline(`${cls.kind()}/${directoryName}.log`, cls.report(cp.spawnSync(`node`, args, { cwd, timeout, shell: true }), cwd));
class DockerfileRunner extends ExternalCompileRunnerBase {
readonly testDir = "tests/cases/docker/";
kind(): TestRunnerKind {
return "docker";
}
initializeTests(): void {
// Read in and evaluate the test list
const testList = this.tests && this.tests.length ? this.tests : this.getTestFiles();
// eslint-disable-next-line @typescript-eslint/no-this-alias
const cls = this;
describe(`${this.kind()} code samples`, function(this: Mocha.ISuiteCallbackContext) {
this.timeout(cls.timeout); // 20 minutes
before(() => {
cls.exec("docker", ["build", ".", "-t", "typescript/typescript"], { cwd: Harness.IO.getWorkspaceRoot() }); // cached because workspace is hashed to determine cacheability
});
for (const test of testList) {
const directory = typeof test === "string" ? test : test.file;
const cwd = path.join(Harness.IO.getWorkspaceRoot(), cls.testDir, directory);
it(`should build ${directory} successfully`, () => {
const imageName = `tstest/${directory}`;
cls.exec("docker", ["build", "--no-cache", ".", "-t", imageName], { cwd }); // --no-cache so the latest version of the repos referenced is always fetched
const cp: typeof import("child_process") = require("child_process");
Harness.Baseline.runBaseline(`${cls.kind()}/${directory}.log`, cls.report(cp.spawnSync(`docker`, ["run", imageName], { cwd, timeout: cls.timeout, shell: true })));
function exec(command: string, args: string[], options: { cwd: string, timeout?: number }): void {
const res = cp.spawnSync(command, args, { timeout, shell: true, stdio, ...options });
if (res.status !== 0) {
throw new Error(`${command} ${args.join(" ")} for ${directoryName} failed: ${res.stderr && res.stderr.toString()}`);
}
}
});
});
}
}
export class UserCodeRunner extends ExternalCompileRunnerBase {
readonly testDir = "tests/cases/user/";
kind(): TestRunnerKind {
return "user";
}
report(result: ExecResult) {
// eslint-disable-next-line no-null/no-null
return result.status === 0 && !result.stdout.length && !result.stderr.length ? null : `Exit Code: ${result.status}
Standard output:
${sortErrors(stripAbsoluteImportPaths(result.stdout.toString().replace(/\r\n/g, "\n")))}
Standard error:
${stripAbsoluteImportPaths(result.stderr.toString().replace(/\r\n/g, "\n"))}`;
}
}
export class DockerfileRunner extends ExternalCompileRunnerBase {
readonly testDir = "tests/cases/docker/";
kind(): TestRunnerKind {
return "docker";
}
initializeTests(): void {
// Read in and evaluate the test list
const testList = this.tests && this.tests.length ? this.tests : this.getTestFiles();
// eslint-disable-next-line @typescript-eslint/no-this-alias
const cls = this;
describe(`${this.kind()} code samples`, function(this: Mocha.ISuiteCallbackContext) {
this.timeout(cls.timeout); // 20 minutes
before(() => {
cls.exec("docker", ["build", ".", "-t", "typescript/typescript"], { cwd: IO.getWorkspaceRoot() }); // cached because workspace is hashed to determine cacheability
});
for (const test of testList) {
const directory = typeof test === "string" ? test : test.file;
const cwd = path.join(IO.getWorkspaceRoot(), cls.testDir, directory);
it(`should build ${directory} successfully`, () => {
const imageName = `tstest/${directory}`;
cls.exec("docker", ["build", "--no-cache", ".", "-t", imageName], { cwd }); // --no-cache so the latest version of the repos referenced is always fetched
const cp: typeof import("child_process") = require("child_process");
Baseline.runBaseline(`${cls.kind()}/${directory}.log`, cls.report(cp.spawnSync(`docker`, ["run", imageName], { cwd, timeout: cls.timeout, shell: true })));
});
}
});
}
private timeout = 1_200_000; // 20 minutes;
private exec(command: string, args: string[], options: { cwd: string, timeout?: number }): void {
const cp: typeof import("child_process") = require("child_process");
const stdio = isWorker ? "pipe" : "inherit";
const res = cp.spawnSync(command, args, { timeout: this.timeout, shell: true, stdio, ...options });
if (res.status !== 0) {
throw new Error(`${command} ${args.join(" ")} for ${options.cwd} failed: ${res.stderr && res.stderr.toString()}`);
}
});
}
}
report(result: ExecResult) {
// eslint-disable-next-line no-null/no-null
return result.status === 0 && !result.stdout.length && !result.stderr.length ? null : `Exit Code: ${result.status}
Standard output:
${sanitizeDockerfileOutput(result.stdout.toString())}
private timeout = 1_200_000; // 20 minutes;
private exec(command: string, args: string[], options: { cwd: string, timeout?: number }): void {
const cp: typeof import("child_process") = require("child_process");
const stdio = isWorker ? "pipe" : "inherit";
const res = cp.spawnSync(command, args, { timeout: this.timeout, shell: true, stdio, ...options });
if (res.status !== 0) {
throw new Error(`${command} ${args.join(" ")} for ${options.cwd} failed: ${res.stderr && res.stderr.toString()}`);
Standard error:
${sanitizeDockerfileOutput(result.stderr.toString())}`;
}
}
report(result: ExecResult) {
// eslint-disable-next-line no-null/no-null
return result.status === 0 && !result.stdout.length && !result.stderr.length ? null : `Exit Code: ${result.status}
Standard output:
${sanitizeDockerfileOutput(result.stdout.toString())}
Standard error:
${sanitizeDockerfileOutput(result.stderr.toString())}`;
function sanitizeDockerfileOutput(result: string): string {
return [
normalizeNewlines,
stripANSIEscapes,
stripRushStageNumbers,
stripWebpackHash,
sanitizeVersionSpecifiers,
sanitizeTimestamps,
sanitizeSizes,
sanitizeUnimportantGulpOutput,
stripAbsoluteImportPaths,
].reduce((result, f) => f(result), result);
}
}
function sanitizeDockerfileOutput(result: string): string {
return [
normalizeNewlines,
stripANSIEscapes,
stripRushStageNumbers,
stripWebpackHash,
sanitizeVersionSpecifiers,
sanitizeTimestamps,
sanitizeSizes,
sanitizeUnimportantGulpOutput,
stripAbsoluteImportPaths,
].reduce((result, f) => f(result), result);
}
function normalizeNewlines(result: string): string {
return result.replace(/\r\n/g, "\n");
}
function stripANSIEscapes(result: string): string {
return result.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, "");
}
function stripRushStageNumbers(result: string): string {
return result.replace(/\d+ of \d+:/g, "XX of XX:");
}
function stripWebpackHash(result: string): string {
return result.replace(/Hash: \w+/g, "Hash: [redacted]");
}
function sanitizeSizes(result: string): string {
return result.replace(/\d+(\.\d+)? ((Ki|M)B|bytes)/g, "X KiB");
}
/**
* Gulp's output order within a `parallel` block is nondeterministic (and there's no way to configure it to execute in series),
* so we purge as much of the gulp output as we can
*/
function sanitizeUnimportantGulpOutput(result: string): string {
return result.replace(/^.*(\] (Starting)|(Finished)).*$/gm, "") // "gulp" task start/end messages (nondeterministic order)
.replace(/^.*(\] . (finished)|(started)).*$/gm, "") // "just" task start/end messages (nondeterministic order)
.replace(/^.*\] Respawned to PID: \d+.*$/gm, "") // PID of child is OS and system-load dependent (likely stableish in a container but still dangerous)
.replace(/\n+/g, "\n");
}
function sanitizeTimestamps(result: string): string {
return result.replace(/\[\d?\d:\d\d:\d\d (A|P)M\]/g, "[XX:XX:XX XM]")
.replace(/\[\d?\d:\d\d:\d\d\]/g, "[XX:XX:XX]")
.replace(/\/\d+-\d+-[\d_TZ]+-debug.log/g, "\/XXXX-XX-XXXXXXXXX-debug.log")
.replace(/\d+\/\d+\/\d+ \d+:\d+:\d+ (AM|PM)/g, "XX/XX/XX XX:XX:XX XM")
.replace(/\d+(\.\d+)? sec(onds?)?/g, "? seconds")
.replace(/\d+(\.\d+)? min(utes?)?/g, "")
.replace(/\d+(\.\d+)? ?m?s/g, "?s")
.replace(/ \(\?s\)/g, "");
}
function sanitizeVersionSpecifiers(result: string): string {
return result
.replace(/\d+.\d+.\d+-insiders.\d\d\d\d\d\d\d\d/g, "X.X.X-insiders.xxxxxxxx")
.replace(/Rush Multi-Project Build Tool (\d+)\.\d+\.\d+/g, "Rush Multi-Project Build Tool $1.X.X")
.replace(/([@v\()])\d+\.\d+\.\d+/g, "$1X.X.X")
.replace(/webpack (\d+)\.\d+\.\d+/g, "webpack $1.X.X")
.replace(/Webpack version: (\d+)\.\d+\.\d+/g, "Webpack version: $1.X.X");
}
/**
* Import types and some other error messages use absolute paths in errors as they have no context to be written relative to;
* This is problematic for error baselines, so we grep for them and strip them out.
*/
function stripAbsoluteImportPaths(result: string) {
const workspaceRegexp = new RegExp(Harness.IO.getWorkspaceRoot().replace(/\\/g, "\\\\"), "g");
return result
.replace(/import\(".*?\/tests\/cases\/user\//g, `import("/`)
.replace(/Module '".*?\/tests\/cases\/user\//g, `Module '"/`)
.replace(workspaceRegexp, "../../..");
}
function sortErrors(result: string) {
return ts.flatten(splitBy(result.split("\n"), s => /^\S+/.test(s)).sort(compareErrorStrings)).join("\n");
}
const errorRegexp = /^(.+\.[tj]sx?)\((\d+),(\d+)\)(: error TS.*)/;
function compareErrorStrings(a: string[], b: string[]) {
ts.Debug.assertGreaterThanOrEqual(a.length, 1);
ts.Debug.assertGreaterThanOrEqual(b.length, 1);
const matchA = a[0].match(errorRegexp);
if (!matchA) {
return -1;
function normalizeNewlines(result: string): string {
return result.replace(/\r\n/g, "\n");
}
const matchB = b[0].match(errorRegexp);
if (!matchB) {
return 1;
function stripANSIEscapes(result: string): string {
return result.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, "");
}
const [, errorFileA, lineNumberStringA, columnNumberStringA, remainderA] = matchA;
const [, errorFileB, lineNumberStringB, columnNumberStringB, remainderB] = matchB;
return ts.comparePathsCaseSensitive(errorFileA, errorFileB) ||
ts.compareValues(parseInt(lineNumberStringA), parseInt(lineNumberStringB)) ||
ts.compareValues(parseInt(columnNumberStringA), parseInt(columnNumberStringB)) ||
ts.compareStringsCaseSensitive(remainderA, remainderB) ||
ts.compareStringsCaseSensitive(a.slice(1).join("\n"), b.slice(1).join("\n"));
}
class DefinitelyTypedRunner extends ExternalCompileRunnerBase {
readonly testDir = "../DefinitelyTyped/types/";
workingDirectory = this.testDir;
kind(): TestRunnerKind {
return "dt";
function stripRushStageNumbers(result: string): string {
return result.replace(/\d+ of \d+:/g, "XX of XX:");
}
report(result: ExecResult, cwd: string) {
const stdout = removeExpectedErrors(result.stdout.toString(), cwd);
const stderr = result.stderr.toString();
// eslint-disable-next-line no-null/no-null
return !stdout.length && !stderr.length ? null : `Exit Code: ${result.status}
Standard output:
${stdout.replace(/\r\n/g, "\n")}
Standard error:
${stderr.replace(/\r\n/g, "\n")}`;
function stripWebpackHash(result: string): string {
return result.replace(/Hash: \w+/g, "Hash: [redacted]");
}
}
function removeExpectedErrors(errors: string, cwd: string): string {
return ts.flatten(splitBy(errors.split("\n"), s => /^\S+/.test(s)).filter(isUnexpectedError(cwd))).join("\n");
}
/**
* Returns true if the line that caused the error contains '$ExpectError',
* or if the line before that one contains '$ExpectError'.
* '$ExpectError' is a marker used in Definitely Typed tests,
* meaning that the error should not contribute toward our error baslines.
*/
function isUnexpectedError(cwd: string) {
return (error: string[]) => {
ts.Debug.assertGreaterThanOrEqual(error.length, 1);
const match = error[0].match(/(.+\.tsx?)\((\d+),\d+\): error TS/);
if (!match) {
return true;
function sanitizeSizes(result: string): string {
return result.replace(/\d+(\.\d+)? ((Ki|M)B|bytes)/g, "X KiB");
}
/**
* Gulp's output order within a `parallel` block is nondeterministic (and there's no way to configure it to execute in series),
* so we purge as much of the gulp output as we can
*/
function sanitizeUnimportantGulpOutput(result: string): string {
return result.replace(/^.*(\] (Starting)|(Finished)).*$/gm, "") // "gulp" task start/end messages (nondeterministic order)
.replace(/^.*(\] . (finished)|(started)).*$/gm, "") // "just" task start/end messages (nondeterministic order)
.replace(/^.*\] Respawned to PID: \d+.*$/gm, "") // PID of child is OS and system-load dependent (likely stableish in a container but still dangerous)
.replace(/\n+/g, "\n");
}
function sanitizeTimestamps(result: string): string {
return result.replace(/\[\d?\d:\d\d:\d\d (A|P)M\]/g, "[XX:XX:XX XM]")
.replace(/\[\d?\d:\d\d:\d\d\]/g, "[XX:XX:XX]")
.replace(/\/\d+-\d+-[\d_TZ]+-debug.log/g, "\/XXXX-XX-XXXXXXXXX-debug.log")
.replace(/\d+\/\d+\/\d+ \d+:\d+:\d+ (AM|PM)/g, "XX/XX/XX XX:XX:XX XM")
.replace(/\d+(\.\d+)? sec(onds?)?/g, "? seconds")
.replace(/\d+(\.\d+)? min(utes?)?/g, "")
.replace(/\d+(\.\d+)? ?m?s/g, "?s")
.replace(/ \(\?s\)/g, "");
}
function sanitizeVersionSpecifiers(result: string): string {
return result
.replace(/\d+.\d+.\d+-insiders.\d\d\d\d\d\d\d\d/g, "X.X.X-insiders.xxxxxxxx")
.replace(/Rush Multi-Project Build Tool (\d+)\.\d+\.\d+/g, "Rush Multi-Project Build Tool $1.X.X")
.replace(/([@v\()])\d+\.\d+\.\d+/g, "$1X.X.X")
.replace(/webpack (\d+)\.\d+\.\d+/g, "webpack $1.X.X")
.replace(/Webpack version: (\d+)\.\d+\.\d+/g, "Webpack version: $1.X.X");
}
/**
* Import types and some other error messages use absolute paths in errors as they have no context to be written relative to;
* This is problematic for error baselines, so we grep for them and strip them out.
*/
function stripAbsoluteImportPaths(result: string) {
const workspaceRegexp = new RegExp(IO.getWorkspaceRoot().replace(/\\/g, "\\\\"), "g");
return result
.replace(/import\(".*?\/tests\/cases\/user\//g, `import("/`)
.replace(/Module '".*?\/tests\/cases\/user\//g, `Module '"/`)
.replace(workspaceRegexp, "../../..");
}
function sortErrors(result: string) {
return ts.flatten(splitBy(result.split("\n"), s => /^\S+/.test(s)).sort(compareErrorStrings)).join("\n");
}
const errorRegexp = /^(.+\.[tj]sx?)\((\d+),(\d+)\)(: error TS.*)/;
function compareErrorStrings(a: string[], b: string[]) {
ts.Debug.assertGreaterThanOrEqual(a.length, 1);
ts.Debug.assertGreaterThanOrEqual(b.length, 1);
const matchA = a[0].match(errorRegexp);
if (!matchA) {
return -1;
}
const [, errorFile, lineNumberString] = match;
const lines = fs.readFileSync(path.join(cwd, errorFile), { encoding: "utf8" }).split("\n");
const lineNumber = parseInt(lineNumberString) - 1;
ts.Debug.assertGreaterThanOrEqual(lineNumber, 0);
ts.Debug.assertLessThan(lineNumber, lines.length);
const previousLine = lineNumber - 1 > 0 ? lines[lineNumber - 1] : "";
return !ts.stringContains(lines[lineNumber], "$ExpectError") && !ts.stringContains(previousLine, "$ExpectError");
};
}
/**
* Split an array into multiple arrays whenever `isStart` returns true.
* @example
* splitBy([1,2,3,4,5,6], isOdd)
* ==> [[1, 2], [3, 4], [5, 6]]
* where
* const isOdd = n => !!(n % 2)
*/
function splitBy<T>(xs: T[], isStart: (x: T) => boolean): T[][] {
const result = [];
let group: T[] = [];
for (const x of xs) {
if (isStart(x)) {
if (group.length) {
result.push(group);
const matchB = b[0].match(errorRegexp);
if (!matchB) {
return 1;
}
const [, errorFileA, lineNumberStringA, columnNumberStringA, remainderA] = matchA;
const [, errorFileB, lineNumberStringB, columnNumberStringB, remainderB] = matchB;
return ts.comparePathsCaseSensitive(errorFileA, errorFileB) ||
ts.compareValues(parseInt(lineNumberStringA), parseInt(lineNumberStringB)) ||
ts.compareValues(parseInt(columnNumberStringA), parseInt(columnNumberStringB)) ||
ts.compareStringsCaseSensitive(remainderA, remainderB) ||
ts.compareStringsCaseSensitive(a.slice(1).join("\n"), b.slice(1).join("\n"));
}
export class DefinitelyTypedRunner extends ExternalCompileRunnerBase {
readonly testDir = "../DefinitelyTyped/types/";
workingDirectory = this.testDir;
kind(): TestRunnerKind {
return "dt";
}
report(result: ExecResult, cwd: string) {
const stdout = removeExpectedErrors(result.stdout.toString(), cwd);
const stderr = result.stderr.toString();
// eslint-disable-next-line no-null/no-null
return !stdout.length && !stderr.length ? null : `Exit Code: ${result.status}
Standard output:
${stdout.replace(/\r\n/g, "\n")}
Standard error:
${stderr.replace(/\r\n/g, "\n")}`;
}
}
function removeExpectedErrors(errors: string, cwd: string): string {
return ts.flatten(splitBy(errors.split("\n"), s => /^\S+/.test(s)).filter(isUnexpectedError(cwd))).join("\n");
}
/**
* Returns true if the line that caused the error contains '$ExpectError',
* or if the line before that one contains '$ExpectError'.
* '$ExpectError' is a marker used in Definitely Typed tests,
* meaning that the error should not contribute toward our error baslines.
*/
function isUnexpectedError(cwd: string) {
return (error: string[]) => {
ts.Debug.assertGreaterThanOrEqual(error.length, 1);
const match = error[0].match(/(.+\.tsx?)\((\d+),\d+\): error TS/);
if (!match) {
return true;
}
group = [x];
}
else {
group.push(x);
}
const [, errorFile, lineNumberString] = match;
const lines = fs.readFileSync(path.join(cwd, errorFile), { encoding: "utf8" }).split("\n");
const lineNumber = parseInt(lineNumberString) - 1;
ts.Debug.assertGreaterThanOrEqual(lineNumber, 0);
ts.Debug.assertLessThan(lineNumber, lines.length);
const previousLine = lineNumber - 1 > 0 ? lines[lineNumber - 1] : "";
return !ts.stringContains(lines[lineNumber], "$ExpectError") && !ts.stringContains(previousLine, "$ExpectError");
};
}
if (group.length) {
result.push(group);
/**
* Split an array into multiple arrays whenever `isStart` returns true.
* @example
* splitBy([1,2,3,4,5,6], isOdd)
* ==> [[1, 2], [3, 4], [5, 6]]
* where
* const isOdd = n => !!(n % 2)
*/
function splitBy<T>(xs: T[], isStart: (x: T) => boolean): T[][] {
const result = [];
let group: T[] = [];
for (const x of xs) {
if (isStart(x)) {
if (group.length) {
result.push(group);
}
group = [x];
}
else {
group.push(x);
}
}
if (group.length) {
result.push(group);
}
return result;
}
return result;
}
}

View File

@ -0,0 +1,2 @@
// empty ref to fakes so it can be referenced by unittests
namespace fakes {}

View File

@ -0,0 +1,2 @@
// empty ref to FourSlash so it can be referenced by unittests
namespace FourSlash {}

View File

@ -1,70 +1,72 @@
class FourSlashRunner extends RunnerBase {
protected basePath: string;
protected testSuiteName: TestRunnerKind;
namespace Harness {
export class FourSlashRunner extends RunnerBase {
protected basePath: string;
protected testSuiteName: TestRunnerKind;
constructor(private testType: FourSlash.FourSlashTestType) {
super();
switch (testType) {
case FourSlash.FourSlashTestType.Native:
this.basePath = "tests/cases/fourslash";
this.testSuiteName = "fourslash";
break;
case FourSlash.FourSlashTestType.Shims:
this.basePath = "tests/cases/fourslash/shims";
this.testSuiteName = "fourslash-shims";
break;
case FourSlash.FourSlashTestType.ShimsWithPreprocess:
this.basePath = "tests/cases/fourslash/shims-pp";
this.testSuiteName = "fourslash-shims-pp";
break;
case FourSlash.FourSlashTestType.Server:
this.basePath = "tests/cases/fourslash/server";
this.testSuiteName = "fourslash-server";
break;
default:
throw ts.Debug.assertNever(testType);
}
}
public enumerateTestFiles() {
// see also: `enumerateTestFiles` in tests/webTestServer.ts
return this.enumerateFiles(this.basePath, /\.ts/i, { recursive: false });
}
public kind() {
return this.testSuiteName;
}
public initializeTests() {
if (this.tests.length === 0) {
this.tests = Harness.IO.enumerateTestFiles(this);
constructor(private testType: FourSlash.FourSlashTestType) {
super();
switch (testType) {
case FourSlash.FourSlashTestType.Native:
this.basePath = "tests/cases/fourslash";
this.testSuiteName = "fourslash";
break;
case FourSlash.FourSlashTestType.Shims:
this.basePath = "tests/cases/fourslash/shims";
this.testSuiteName = "fourslash-shims";
break;
case FourSlash.FourSlashTestType.ShimsWithPreprocess:
this.basePath = "tests/cases/fourslash/shims-pp";
this.testSuiteName = "fourslash-shims-pp";
break;
case FourSlash.FourSlashTestType.Server:
this.basePath = "tests/cases/fourslash/server";
this.testSuiteName = "fourslash-server";
break;
default:
throw ts.Debug.assertNever(testType);
}
}
describe(this.testSuiteName + " tests", () => {
this.tests.forEach(test => {
const file = typeof test === "string" ? test : test.file;
describe(file, () => {
let fn = ts.normalizeSlashes(file);
const justName = fn.replace(/^.*[\\\/]/, "");
public enumerateTestFiles() {
// see also: `enumerateTestFiles` in tests/webTestServer.ts
return this.enumerateFiles(this.basePath, /\.ts/i, { recursive: false });
}
// Convert to relative path
const testIndex = fn.indexOf("tests/");
if (testIndex >= 0) fn = fn.substr(testIndex);
public kind() {
return this.testSuiteName;
}
if (justName && !justName.match(/fourslash\.ts$/i) && !justName.match(/\.d\.ts$/i)) {
it(this.testSuiteName + " test " + justName + " runs correctly", () => {
FourSlash.runFourSlashTest(this.basePath, this.testType, fn);
});
}
public initializeTests() {
if (this.tests.length === 0) {
this.tests = IO.enumerateTestFiles(this);
}
describe(this.testSuiteName + " tests", () => {
this.tests.forEach(test => {
const file = typeof test === "string" ? test : test.file;
describe(file, () => {
let fn = ts.normalizeSlashes(file);
const justName = fn.replace(/^.*[\\\/]/, "");
// Convert to relative path
const testIndex = fn.indexOf("tests/");
if (testIndex >= 0) fn = fn.substr(testIndex);
if (justName && !justName.match(/fourslash\.ts$/i) && !justName.match(/\.d\.ts$/i)) {
it(this.testSuiteName + " test " + justName + " runs correctly", () => {
FourSlash.runFourSlashTest(this.basePath, this.testType, fn);
});
}
});
});
});
});
}
}
}
class GeneratedFourslashRunner extends FourSlashRunner {
constructor(testType: FourSlash.FourSlashTestType) {
super(testType);
this.basePath += "/generated/";
export class GeneratedFourslashRunner extends FourSlashRunner {
constructor(testType: FourSlash.FourSlashTestType) {
super(testType);
this.basePath += "/generated/";
}
}
}

View File

@ -240,7 +240,7 @@ namespace Harness.Parallel.Host {
let totalPassing = 0;
const startDate = new Date();
const progressBars = new ProgressBars({ noColors });
const progressBars = new ProgressBars({ noColors: Harness.noColors }); // eslint-disable-line @typescript-eslint/no-unnecessary-qualifier
const progressUpdateInterval = 1 / progressBars._options.width;
let nextProgress = progressUpdateInterval;
@ -250,7 +250,7 @@ namespace Harness.Parallel.Host {
let closedWorkers = 0;
for (let i = 0; i < workerCount; i++) {
// TODO: Just send the config over the IPC channel or in the command line arguments
const config: TestConfig = { light: lightMode, listenForWork: true, runUnitTests, stackTraceLimit, timeout: globalTimeout };
const config: TestConfig = { light: lightMode, listenForWork: true, runUnitTests: Harness.runUnitTests, stackTraceLimit: Harness.stackTraceLimit, timeout: globalTimeout }; // eslint-disable-line @typescript-eslint/no-unnecessary-qualifier
const configPath = ts.combinePaths(taskConfigsFolder, `task-config${i}.json`);
IO.writeFile(configPath, JSON.stringify(config));
const worker: Worker = {
@ -549,7 +549,7 @@ namespace Harness.Parallel.Host {
failedTestReporter = new FailedTestReporter(replayRunner, {
reporterOptions: {
file: path.resolve(".failed-tests"),
keepFailed
keepFailed: Harness.keepFailed // eslint-disable-line @typescript-eslint/no-unnecessary-qualifier
}
});
}

View File

@ -0,0 +1,2 @@
// empty ref to Playback so it can be referenced by unittests
namespace Playback {}

View File

@ -30,16 +30,16 @@ namespace project {
outputFiles?: readonly documents.TextDocument[];
}
export class ProjectRunner extends RunnerBase {
export class ProjectRunner extends Harness.RunnerBase {
public enumerateTestFiles() {
const all = this.enumerateFiles("tests/cases/project", /\.json$/, { recursive: true });
if (shards === 1) {
if (Harness.shards === 1) {
return all;
}
return all.filter((_val, idx) => idx % shards === (shardId - 1));
return all.filter((_val, idx) => idx % Harness.shards === (Harness.shardId - 1));
}
public kind(): TestRunnerKind {
public kind(): Harness.TestRunnerKind {
return "project";
}
@ -207,13 +207,13 @@ namespace project {
const resolutionInfo: ProjectRunnerTestCaseResolutionInfo & ts.CompilerOptions = JSON.parse(JSON.stringify(this.testCase));
resolutionInfo.resolvedInputFiles = this.compilerResult.program!.getSourceFiles()
.map(({ fileName: input }) =>
vpath.beneath(vfs.builtFolder, input, this.vfs.ignoreCase) || vpath.beneath(vfs.testLibFolder, input, this.vfs.ignoreCase) ? utils.removeTestPathPrefixes(input) :
vpath.beneath(vfs.builtFolder, input, this.vfs.ignoreCase) || vpath.beneath(vfs.testLibFolder, input, this.vfs.ignoreCase) ? Utils.removeTestPathPrefixes(input) :
vpath.isAbsolute(input) ? vpath.relative(cwd, input, ignoreCase) :
input);
resolutionInfo.emittedFiles = this.compilerResult.outputFiles!
.map(output => output.meta.get("fileName") || output.file)
.map(output => utils.removeTestPathPrefixes(vpath.isAbsolute(output) ? vpath.relative(cwd, output, ignoreCase) : output));
.map(output => Utils.removeTestPathPrefixes(vpath.isAbsolute(output) ? vpath.relative(cwd, output, ignoreCase) : output));
const content = JSON.stringify(resolutionInfo, undefined, " ");
Harness.Baseline.runBaseline(this.getBaselineFolder(this.compilerResult.moduleKind) + this.testCaseJustName + ".json", content);
@ -246,7 +246,7 @@ namespace project {
nonSubfolderDiskFiles++;
}
const content = utils.removeTestPathPrefixes(output.text, /*retainTrailingDirectorySeparator*/ true);
const content = Utils.removeTestPathPrefixes(output.text, /*retainTrailingDirectorySeparator*/ true);
Harness.Baseline.runBaseline(this.getBaselineFolder(this.compilerResult.moduleKind) + diskRelativeName, content as string | null); // TODO: GH#18217
}
catch (e) {
@ -412,7 +412,7 @@ namespace project {
const inputFiles = inputSourceFiles.map<Harness.Compiler.TestFile>(sourceFile => ({
unitName: ts.isRootedDiskPath(sourceFile.fileName) ?
RunnerBase.removeFullPaths(sourceFile.fileName) :
Harness.RunnerBase.removeFullPaths(sourceFile.fileName) :
sourceFile.fileName,
content: sourceFile.text
}));

View File

@ -1,257 +1,259 @@
/* eslint-disable prefer-const */
let runners: RunnerBase[] = [];
let iterations = 1;
/* eslint-enable prefer-const */
namespace Harness {
/* eslint-disable prefer-const */
export let runners: RunnerBase[] = [];
export let iterations = 1;
/* eslint-enable prefer-const */
function runTests(runners: RunnerBase[]) {
for (let i = iterations; i > 0; i--) {
for (const runner of runners) {
runner.initializeTests();
}
}
}
function tryGetConfig(args: string[]) {
const prefix = "--config=";
const configPath = ts.forEach(args, arg => arg.lastIndexOf(prefix, 0) === 0 && arg.substr(prefix.length));
// strip leading and trailing quotes from the path (necessary on Windows since shell does not do it automatically)
return configPath && configPath.replace(/(^[\"'])|([\"']$)/g, "");
}
function createRunner(kind: TestRunnerKind): RunnerBase {
switch (kind) {
case "conformance":
return new CompilerBaselineRunner(CompilerTestType.Conformance);
case "compiler":
return new CompilerBaselineRunner(CompilerTestType.Regressions);
case "fourslash":
return new FourSlashRunner(FourSlash.FourSlashTestType.Native);
case "fourslash-shims":
return new FourSlashRunner(FourSlash.FourSlashTestType.Shims);
case "fourslash-shims-pp":
return new FourSlashRunner(FourSlash.FourSlashTestType.ShimsWithPreprocess);
case "fourslash-server":
return new FourSlashRunner(FourSlash.FourSlashTestType.Server);
case "project":
return new project.ProjectRunner();
case "rwc":
return new RWCRunner();
case "test262":
return new Test262BaselineRunner();
case "user":
return new UserCodeRunner();
case "dt":
return new DefinitelyTypedRunner();
case "docker":
return new DockerfileRunner();
}
return ts.Debug.fail(`Unknown runner kind ${kind}`);
}
// users can define tests to run in mytest.config that will override cmd line args, otherwise use cmd line args (test.config), otherwise no options
const mytestconfigFileName = "mytest.config";
const testconfigFileName = "test.config";
const customConfig = tryGetConfig(Harness.IO.args());
const testConfigContent =
customConfig && Harness.IO.fileExists(customConfig)
? Harness.IO.readFile(customConfig)!
: Harness.IO.fileExists(mytestconfigFileName)
? Harness.IO.readFile(mytestconfigFileName)!
: Harness.IO.fileExists(testconfigFileName) ? Harness.IO.readFile(testconfigFileName)! : "";
let taskConfigsFolder: string;
let workerCount: number;
let runUnitTests: boolean | undefined;
let stackTraceLimit: number | "full" | undefined;
let noColors = false;
let keepFailed = false;
interface TestConfig {
light?: boolean;
taskConfigsFolder?: string;
listenForWork?: boolean;
workerCount?: number;
stackTraceLimit?: number | "full";
test?: string[];
runners?: string[];
runUnitTests?: boolean;
noColors?: boolean;
timeout?: number;
keepFailed?: boolean;
shardId?: number;
shards?: number;
}
interface TaskSet {
runner: TestRunnerKind;
files: string[];
}
let configOption: string;
let globalTimeout: number;
function handleTestConfig() {
if (testConfigContent !== "") {
const testConfig = <TestConfig>JSON.parse(testConfigContent);
if (testConfig.light) {
Harness.lightMode = true;
}
if (testConfig.timeout) {
globalTimeout = testConfig.timeout;
}
runUnitTests = testConfig.runUnitTests;
if (testConfig.workerCount) {
workerCount = +testConfig.workerCount;
}
if (testConfig.taskConfigsFolder) {
taskConfigsFolder = testConfig.taskConfigsFolder;
}
if (testConfig.noColors !== undefined) {
noColors = testConfig.noColors;
}
if (testConfig.keepFailed) {
keepFailed = true;
}
if (testConfig.shardId) {
shardId = testConfig.shardId;
}
if (testConfig.shards) {
shards = testConfig.shards;
}
if (testConfig.stackTraceLimit === "full") {
(<any>Error).stackTraceLimit = Infinity;
stackTraceLimit = testConfig.stackTraceLimit;
}
else if ((+testConfig.stackTraceLimit! | 0) > 0) {
(<any>Error).stackTraceLimit = +testConfig.stackTraceLimit! | 0;
stackTraceLimit = +testConfig.stackTraceLimit! | 0;
}
if (testConfig.listenForWork) {
return true;
}
const runnerConfig = testConfig.runners || testConfig.test;
if (runnerConfig && runnerConfig.length > 0) {
if (testConfig.runners) {
runUnitTests = runnerConfig.indexOf("unittest") !== -1;
}
for (const option of runnerConfig) {
if (!option) {
continue;
}
if (!configOption) {
configOption = option;
}
else {
configOption += "+" + option;
}
switch (option) {
case "compiler":
runners.push(new CompilerBaselineRunner(CompilerTestType.Conformance));
runners.push(new CompilerBaselineRunner(CompilerTestType.Regressions));
break;
case "conformance":
runners.push(new CompilerBaselineRunner(CompilerTestType.Conformance));
break;
case "project":
runners.push(new project.ProjectRunner());
break;
case "fourslash":
runners.push(new FourSlashRunner(FourSlash.FourSlashTestType.Native));
break;
case "fourslash-shims":
runners.push(new FourSlashRunner(FourSlash.FourSlashTestType.Shims));
break;
case "fourslash-shims-pp":
runners.push(new FourSlashRunner(FourSlash.FourSlashTestType.ShimsWithPreprocess));
break;
case "fourslash-server":
runners.push(new FourSlashRunner(FourSlash.FourSlashTestType.Server));
break;
case "fourslash-generated":
runners.push(new GeneratedFourslashRunner(FourSlash.FourSlashTestType.Native));
break;
case "rwc":
runners.push(new RWCRunner());
break;
case "test262":
runners.push(new Test262BaselineRunner());
break;
case "user":
runners.push(new UserCodeRunner());
break;
case "dt":
runners.push(new DefinitelyTypedRunner());
break;
case "docker":
runners.push(new DockerfileRunner());
break;
}
function runTests(runners: RunnerBase[]) {
for (let i = iterations; i > 0; i--) {
for (const runner of runners) {
runner.initializeTests();
}
}
}
if (runners.length === 0) {
// compiler
runners.push(new CompilerBaselineRunner(CompilerTestType.Conformance));
runners.push(new CompilerBaselineRunner(CompilerTestType.Regressions));
function tryGetConfig(args: string[]) {
const prefix = "--config=";
const configPath = ts.forEach(args, arg => arg.lastIndexOf(prefix, 0) === 0 && arg.substr(prefix.length));
// strip leading and trailing quotes from the path (necessary on Windows since shell does not do it automatically)
return configPath && configPath.replace(/(^[\"'])|([\"']$)/g, "");
}
runners.push(new project.ProjectRunner());
export function createRunner(kind: TestRunnerKind): RunnerBase {
switch (kind) {
case "conformance":
return new CompilerBaselineRunner(CompilerTestType.Conformance);
case "compiler":
return new CompilerBaselineRunner(CompilerTestType.Regressions);
case "fourslash":
return new FourSlashRunner(FourSlash.FourSlashTestType.Native);
case "fourslash-shims":
return new FourSlashRunner(FourSlash.FourSlashTestType.Shims);
case "fourslash-shims-pp":
return new FourSlashRunner(FourSlash.FourSlashTestType.ShimsWithPreprocess);
case "fourslash-server":
return new FourSlashRunner(FourSlash.FourSlashTestType.Server);
case "project":
return new project.ProjectRunner();
case "rwc":
return new RWC.RWCRunner();
case "test262":
return new Test262BaselineRunner();
case "user":
return new UserCodeRunner();
case "dt":
return new DefinitelyTypedRunner();
case "docker":
return new DockerfileRunner();
}
return ts.Debug.fail(`Unknown runner kind ${kind}`);
}
// language services
runners.push(new FourSlashRunner(FourSlash.FourSlashTestType.Native));
runners.push(new FourSlashRunner(FourSlash.FourSlashTestType.Shims));
runners.push(new FourSlashRunner(FourSlash.FourSlashTestType.ShimsWithPreprocess));
runners.push(new FourSlashRunner(FourSlash.FourSlashTestType.Server));
// runners.push(new GeneratedFourslashRunner());
// users can define tests to run in mytest.config that will override cmd line args, otherwise use cmd line args (test.config), otherwise no options
// CRON-only tests
if (process.env.TRAVIS_EVENT_TYPE === "cron") {
runners.push(new UserCodeRunner());
runners.push(new DockerfileRunner());
const mytestconfigFileName = "mytest.config";
const testconfigFileName = "test.config";
const customConfig = tryGetConfig(IO.args());
const testConfigContent =
customConfig && IO.fileExists(customConfig)
? IO.readFile(customConfig)!
: IO.fileExists(mytestconfigFileName)
? IO.readFile(mytestconfigFileName)!
: IO.fileExists(testconfigFileName) ? IO.readFile(testconfigFileName)! : "";
export let taskConfigsFolder: string;
export let workerCount: number;
export let runUnitTests: boolean | undefined;
export let stackTraceLimit: number | "full" | undefined;
export let noColors = false;
export let keepFailed = false;
export interface TestConfig {
light?: boolean;
taskConfigsFolder?: string;
listenForWork?: boolean;
workerCount?: number;
stackTraceLimit?: number | "full";
test?: string[];
runners?: string[];
runUnitTests?: boolean;
noColors?: boolean;
timeout?: number;
keepFailed?: boolean;
shardId?: number;
shards?: number;
}
export interface TaskSet {
runner: TestRunnerKind;
files: string[];
}
export let configOption: string;
export let globalTimeout: number;
function handleTestConfig() {
if (testConfigContent !== "") {
const testConfig = <TestConfig>JSON.parse(testConfigContent);
if (testConfig.light) {
setLightMode(true);
}
if (testConfig.timeout) {
globalTimeout = testConfig.timeout;
}
runUnitTests = testConfig.runUnitTests;
if (testConfig.workerCount) {
workerCount = +testConfig.workerCount;
}
if (testConfig.taskConfigsFolder) {
taskConfigsFolder = testConfig.taskConfigsFolder;
}
if (testConfig.noColors !== undefined) {
noColors = testConfig.noColors;
}
if (testConfig.keepFailed) {
keepFailed = true;
}
if (testConfig.shardId) {
setShardId(testConfig.shardId);
}
if (testConfig.shards) {
setShards(testConfig.shards);
}
if (testConfig.stackTraceLimit === "full") {
(<any>Error).stackTraceLimit = Infinity;
stackTraceLimit = testConfig.stackTraceLimit;
}
else if ((+testConfig.stackTraceLimit! | 0) > 0) {
(<any>Error).stackTraceLimit = +testConfig.stackTraceLimit! | 0;
stackTraceLimit = +testConfig.stackTraceLimit! | 0;
}
if (testConfig.listenForWork) {
return true;
}
const runnerConfig = testConfig.runners || testConfig.test;
if (runnerConfig && runnerConfig.length > 0) {
if (testConfig.runners) {
runUnitTests = runnerConfig.indexOf("unittest") !== -1;
}
for (const option of runnerConfig) {
if (!option) {
continue;
}
if (!configOption) {
configOption = option;
}
else {
configOption += "+" + option;
}
switch (option) {
case "compiler":
runners.push(new CompilerBaselineRunner(CompilerTestType.Conformance));
runners.push(new CompilerBaselineRunner(CompilerTestType.Regressions));
break;
case "conformance":
runners.push(new CompilerBaselineRunner(CompilerTestType.Conformance));
break;
case "project":
runners.push(new project.ProjectRunner());
break;
case "fourslash":
runners.push(new FourSlashRunner(FourSlash.FourSlashTestType.Native));
break;
case "fourslash-shims":
runners.push(new FourSlashRunner(FourSlash.FourSlashTestType.Shims));
break;
case "fourslash-shims-pp":
runners.push(new FourSlashRunner(FourSlash.FourSlashTestType.ShimsWithPreprocess));
break;
case "fourslash-server":
runners.push(new FourSlashRunner(FourSlash.FourSlashTestType.Server));
break;
case "fourslash-generated":
runners.push(new GeneratedFourslashRunner(FourSlash.FourSlashTestType.Native));
break;
case "rwc":
runners.push(new RWC.RWCRunner());
break;
case "test262":
runners.push(new Test262BaselineRunner());
break;
case "user":
runners.push(new UserCodeRunner());
break;
case "dt":
runners.push(new DefinitelyTypedRunner());
break;
case "docker":
runners.push(new DockerfileRunner());
break;
}
}
}
}
if (runners.length === 0) {
// compiler
runners.push(new CompilerBaselineRunner(CompilerTestType.Conformance));
runners.push(new CompilerBaselineRunner(CompilerTestType.Regressions));
runners.push(new project.ProjectRunner());
// language services
runners.push(new FourSlashRunner(FourSlash.FourSlashTestType.Native));
runners.push(new FourSlashRunner(FourSlash.FourSlashTestType.Shims));
runners.push(new FourSlashRunner(FourSlash.FourSlashTestType.ShimsWithPreprocess));
runners.push(new FourSlashRunner(FourSlash.FourSlashTestType.Server));
// runners.push(new GeneratedFourslashRunner());
// CRON-only tests
if (process.env.TRAVIS_EVENT_TYPE === "cron") {
runners.push(new UserCodeRunner());
runners.push(new DockerfileRunner());
}
}
if (runUnitTests === undefined) {
runUnitTests = runners.length !== 1; // Don't run unit tests when running only one runner if unit tests were not explicitly asked for
}
return false;
}
function beginTests() {
if (ts.Debug.isDebugging) {
ts.Debug.enableDebugInfo();
}
// run tests in en-US by default.
let savedUILocale: string | undefined;
beforeEach(() => {
savedUILocale = ts.getUILocale();
ts.setUILocale("en-US");
});
afterEach(() => ts.setUILocale(savedUILocale));
runTests(runners);
if (!runUnitTests) {
// patch `describe` to skip unit tests
(global as any).describe = ts.noop;
}
}
if (runUnitTests === undefined) {
runUnitTests = runners.length !== 1; // Don't run unit tests when running only one runner if unit tests were not explicitly asked for
}
return false;
}
function beginTests() {
if (ts.Debug.isDebugging) {
ts.Debug.enableDebugInfo();
export let isWorker: boolean;
function startTestEnvironment() {
isWorker = handleTestConfig();
if (isWorker) {
return Parallel.Worker.start();
}
else if (taskConfigsFolder && workerCount && workerCount > 1) {
return Parallel.Host.start();
}
beginTests();
}
// run tests in en-US by default.
let savedUILocale: string | undefined;
beforeEach(() => {
savedUILocale = ts.getUILocale();
ts.setUILocale("en-US");
});
afterEach(() => ts.setUILocale(savedUILocale));
runTests(runners);
if (!runUnitTests) {
// patch `describe` to skip unit tests
(global as any).describe = ts.noop;
}
}
let isWorker: boolean;
function startTestEnvironment() {
isWorker = handleTestConfig();
if (isWorker) {
return Harness.Parallel.Worker.start();
}
else if (taskConfigsFolder && workerCount && workerCount > 1) {
return Harness.Parallel.Host.start();
}
beginTests();
}
startTestEnvironment();
startTestEnvironment();
}

View File

@ -1,18 +1,18 @@
// In harness baselines, null is different than undefined. See `generateActual` in `harness.ts`.
namespace RWC {
function runWithIOLog(ioLog: IoLog, fn: (oldIO: Harness.IO) => void) {
function runWithIOLog(ioLog: Playback.IoLog, fn: (oldIO: Harness.IO) => void) {
const oldIO = Harness.IO;
const wrappedIO = Playback.wrapIO(oldIO);
wrappedIO.startReplayFromData(ioLog);
Harness.IO = wrappedIO;
Harness.setHarnessIO(wrappedIO);
try {
fn(oldIO);
}
finally {
wrappedIO.endReplay();
Harness.IO = oldIO;
Harness.setHarnessIO(oldIO);
}
}
@ -51,7 +51,7 @@ namespace RWC {
this.timeout(800_000); // Allow long timeouts for RWC compilations
let opts!: ts.ParsedCommandLine;
const ioLog: IoLog = Playback.newStyleLogIntoOldStyleLog(JSON.parse(Harness.IO.readFile(`internal/cases/rwc/${jsonPath}/test.json`)!), Harness.IO, `internal/cases/rwc/${baseName}`);
const ioLog: Playback.IoLog = Playback.newStyleLogIntoOldStyleLog(JSON.parse(Harness.IO.readFile(`internal/cases/rwc/${jsonPath}/test.json`)!), Harness.IO, `internal/cases/rwc/${baseName}`);
currentDirectory = ioLog.currentDirectory;
useCustomLibraryFile = !!ioLog.useCustomLibraryFile;
runWithIOLog(ioLog, () => {
@ -207,29 +207,29 @@ namespace RWC {
});
});
}
}
class RWCRunner extends RunnerBase {
public enumerateTestFiles() {
// see also: `enumerateTestFiles` in tests/webTestServer.ts
return Harness.IO.getDirectories("internal/cases/rwc/");
}
export class RWCRunner extends Harness.RunnerBase {
public enumerateTestFiles() {
// see also: `enumerateTestFiles` in tests/webTestServer.ts
return Harness.IO.getDirectories("internal/cases/rwc/");
}
public kind(): TestRunnerKind {
return "rwc";
}
public kind(): Harness.TestRunnerKind {
return "rwc";
}
/** Setup the runner's tests so that they are ready to be executed by the harness
* The first test should be a describe/it block that sets up the harness's compiler instance appropriately
*/
public initializeTests(): void {
// Read in and evaluate the test list
for (const test of this.tests && this.tests.length ? this.tests : this.getTestFiles()) {
this.runTest(typeof test === "string" ? test : test.file);
/** Setup the runner's tests so that they are ready to be executed by the harness
* The first test should be a describe/it block that sets up the harness's compiler instance appropriately
*/
public initializeTests(): void {
// Read in and evaluate the test list
for (const test of this.tests && this.tests.length ? this.tests : this.getTestFiles()) {
this.runTest(typeof test === "string" ? test : test.file);
}
}
private runTest(jsonFileName: string) {
runRWCTest(jsonFileName);
}
}
private runTest(jsonFileName: string) {
RWC.runRWCTest(jsonFileName);
}
}

View File

@ -1,108 +1,110 @@
// In harness baselines, null is different than undefined. See `generateActual` in `harness.ts`.
class Test262BaselineRunner extends RunnerBase {
private static readonly basePath = "internal/cases/test262";
private static readonly helpersFilePath = "tests/cases/test262-harness/helpers.d.ts";
private static readonly helperFile: Harness.Compiler.TestFile = {
unitName: Test262BaselineRunner.helpersFilePath,
content: Harness.IO.readFile(Test262BaselineRunner.helpersFilePath)!,
};
private static readonly testFileExtensionRegex = /\.js$/;
private static readonly options: ts.CompilerOptions = {
allowNonTsExtensions: true,
target: ts.ScriptTarget.Latest,
module: ts.ModuleKind.CommonJS
};
private static readonly baselineOptions: Harness.Baseline.BaselineOptions = {
Subfolder: "test262",
Baselinefolder: "internal/baselines"
};
namespace Harness {
// In harness baselines, null is different than undefined. See `generateActual` in `harness.ts`.
export class Test262BaselineRunner extends RunnerBase {
private static readonly basePath = "internal/cases/test262";
private static readonly helpersFilePath = "tests/cases/test262-harness/helpers.d.ts";
private static readonly helperFile: Compiler.TestFile = {
unitName: Test262BaselineRunner.helpersFilePath,
content: IO.readFile(Test262BaselineRunner.helpersFilePath)!,
};
private static readonly testFileExtensionRegex = /\.js$/;
private static readonly options: ts.CompilerOptions = {
allowNonTsExtensions: true,
target: ts.ScriptTarget.Latest,
module: ts.ModuleKind.CommonJS
};
private static readonly baselineOptions: Baseline.BaselineOptions = {
Subfolder: "test262",
Baselinefolder: "internal/baselines"
};
private static getTestFilePath(filename: string): string {
return Test262BaselineRunner.basePath + "/" + filename;
}
private static getTestFilePath(filename: string): string {
return Test262BaselineRunner.basePath + "/" + filename;
}
private runTest(filePath: string) {
describe("test262 test for " + filePath, () => {
// 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 testState: {
filename: string;
compilerResult: compiler.CompilationResult;
inputFiles: Harness.Compiler.TestFile[];
};
before(() => {
const content = Harness.IO.readFile(filePath)!;
const testFilename = ts.removeFileExtension(filePath).replace(/\//g, "_") + ".test";
const testCaseContent = Harness.TestCaseParser.makeUnitsFromTest(content, testFilename);
const inputFiles: Harness.Compiler.TestFile[] = testCaseContent.testUnitData.map(unit => {
const unitName = Test262BaselineRunner.getTestFilePath(unit.name);
return { unitName, content: unit.content };
});
// Emit the results
testState = {
filename: testFilename,
inputFiles,
compilerResult: undefined!, // TODO: GH#18217
private runTest(filePath: string) {
describe("test262 test for " + filePath, () => {
// 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 testState: {
filename: string;
compilerResult: compiler.CompilationResult;
inputFiles: Compiler.TestFile[];
};
testState.compilerResult = Harness.Compiler.compileFiles(
[Test262BaselineRunner.helperFile].concat(inputFiles),
/*otherFiles*/ [],
/* harnessOptions */ undefined,
Test262BaselineRunner.options,
/* currentDirectory */ undefined);
});
before(() => {
const content = IO.readFile(filePath)!;
const testFilename = ts.removeFileExtension(filePath).replace(/\//g, "_") + ".test";
const testCaseContent = TestCaseParser.makeUnitsFromTest(content, testFilename);
after(() => {
testState = undefined!;
});
const inputFiles: Compiler.TestFile[] = testCaseContent.testUnitData.map(unit => {
const unitName = Test262BaselineRunner.getTestFilePath(unit.name);
return { unitName, content: unit.content };
});
it("has the expected emitted code", () => {
const files = Array.from(testState.compilerResult.js.values()).filter(f => f.file !== Test262BaselineRunner.helpersFilePath);
Harness.Baseline.runBaseline(testState.filename + ".output.js", Harness.Compiler.collateOutputs(files), Test262BaselineRunner.baselineOptions);
});
// Emit the results
testState = {
filename: testFilename,
inputFiles,
compilerResult: undefined!, // TODO: GH#18217
};
it("has the expected errors", () => {
const errors = testState.compilerResult.diagnostics;
// eslint-disable-next-line no-null/no-null
const baseline = errors.length === 0 ? null : Harness.Compiler.getErrorBaseline(testState.inputFiles, errors);
Harness.Baseline.runBaseline(testState.filename + ".errors.txt", baseline, Test262BaselineRunner.baselineOptions);
});
testState.compilerResult = Compiler.compileFiles(
[Test262BaselineRunner.helperFile].concat(inputFiles),
/*otherFiles*/ [],
/* harnessOptions */ undefined,
Test262BaselineRunner.options,
/* currentDirectory */ undefined);
});
it("satisfies invariants", () => {
const sourceFile = testState.compilerResult.program!.getSourceFile(Test262BaselineRunner.getTestFilePath(testState.filename));
Utils.assertInvariants(sourceFile, /*parent:*/ undefined);
});
after(() => {
testState = undefined!;
});
it("has the expected AST", () => {
const sourceFile = testState.compilerResult.program!.getSourceFile(Test262BaselineRunner.getTestFilePath(testState.filename))!;
Harness.Baseline.runBaseline(testState.filename + ".AST.txt", Utils.sourceFileToJSON(sourceFile), Test262BaselineRunner.baselineOptions);
});
});
}
it("has the expected emitted code", () => {
const files = Array.from(testState.compilerResult.js.values()).filter(f => f.file !== Test262BaselineRunner.helpersFilePath);
Baseline.runBaseline(testState.filename + ".output.js", Compiler.collateOutputs(files), Test262BaselineRunner.baselineOptions);
});
public kind(): TestRunnerKind {
return "test262";
}
it("has the expected errors", () => {
const errors = testState.compilerResult.diagnostics;
// eslint-disable-next-line no-null/no-null
const baseline = errors.length === 0 ? null : Compiler.getErrorBaseline(testState.inputFiles, errors);
Baseline.runBaseline(testState.filename + ".errors.txt", baseline, Test262BaselineRunner.baselineOptions);
});
public enumerateTestFiles() {
// see also: `enumerateTestFiles` in tests/webTestServer.ts
return ts.map(this.enumerateFiles(Test262BaselineRunner.basePath, Test262BaselineRunner.testFileExtensionRegex, { recursive: true }), ts.normalizePath);
}
it("satisfies invariants", () => {
const sourceFile = testState.compilerResult.program!.getSourceFile(Test262BaselineRunner.getTestFilePath(testState.filename));
Utils.assertInvariants(sourceFile, /*parent:*/ undefined);
});
public initializeTests() {
// this will set up a series of describe/it blocks to run between the setup and cleanup phases
if (this.tests.length === 0) {
const testFiles = this.getTestFiles();
testFiles.forEach(fn => {
this.runTest(fn);
it("has the expected AST", () => {
const sourceFile = testState.compilerResult.program!.getSourceFile(Test262BaselineRunner.getTestFilePath(testState.filename))!;
Baseline.runBaseline(testState.filename + ".AST.txt", Utils.sourceFileToJSON(sourceFile), Test262BaselineRunner.baselineOptions);
});
});
}
else {
this.tests.forEach(test => this.runTest(typeof test === "string" ? test : test.file));
public kind(): TestRunnerKind {
return "test262";
}
public enumerateTestFiles() {
// see also: `enumerateTestFiles` in tests/webTestServer.ts
return ts.map(this.enumerateFiles(Test262BaselineRunner.basePath, Test262BaselineRunner.testFileExtensionRegex, { recursive: true }), ts.normalizePath);
}
public initializeTests() {
// this will set up a series of describe/it blocks to run between the setup and cleanup phases
if (this.tests.length === 0) {
const testFiles = this.getTestFiles();
testFiles.forEach(fn => {
this.runTest(fn);
});
}
else {
this.tests.forEach(test => this.runTest(typeof test === "string" ? test : test.file));
}
}
}
}
}

View File

@ -24,6 +24,16 @@
],
"files": [
"compilerRef.ts",
"evaluatorRef.ts",
"fakesRef.ts",
"vpathRef.ts",
"vfsRef.ts",
"fourslashRef.ts",
"playbackRef.ts",
"utilsRef.ts",
"documentsRef.ts",
"fourslashRunner.ts",
"compilerRunner.ts",
"projectsRunner.ts",

View File

@ -1,8 +1,4 @@
namespace ts {
// make clear this is a global mutation!
// eslint-disable-next-line @typescript-eslint/no-unnecessary-qualifier
ts.disableIncrementalParsing = false;
function withChange(text: IScriptSnapshot, start: number, length: number, newText: string): { text: IScriptSnapshot; textChangeRange: TextChangeRange; } {
const contents = getSnapshotText(text);
const newContents = contents.substr(0, start) + newText + contents.substring(start + length);

View File

@ -1,5 +1,5 @@
namespace ts {
import theory = utils.theory;
import theory = Utils.theory;
describe("unittests:: semver", () => {
describe("Version", () => {
function assertVersion(version: Version, [major, minor, patch, prerelease, build]: [number, number, number, string[]?, string[]?]) {

View File

@ -1,4 +1,6 @@
namespace ts {
const _chai: typeof import("chai") = require("chai");
const expect: typeof _chai.expect = _chai.expect;
describe("unittests:: services:: languageService", () => {
const files: {[index: string]: string} = {
"foo.ts": `import Vue from "./vue";

View File

@ -4,14 +4,14 @@ namespace ts {
scenario: "javascriptProjectEmit",
subScenario: `loads js-based projects and emits them correctly`,
fs: () => loadProjectFromFiles({
"/src/common/nominal.js": utils.dedent`
"/src/common/nominal.js": Utils.dedent`
/**
* @template T, Name
* @typedef {T & {[Symbol.species]: Name}} Nominal
*/
module.exports = {};
`,
"/src/common/tsconfig.json": utils.dedent`
"/src/common/tsconfig.json": Utils.dedent`
{
"extends": "../tsconfig.base.json",
"compilerOptions": {
@ -19,14 +19,14 @@ namespace ts {
},
"include": ["nominal.js"]
}`,
"/src/sub-project/index.js": utils.dedent`
"/src/sub-project/index.js": Utils.dedent`
import { Nominal } from '../common/nominal';
/**
* @typedef {Nominal<string, 'MyNominal'>} MyNominal
*/
`,
"/src/sub-project/tsconfig.json": utils.dedent`
"/src/sub-project/tsconfig.json": Utils.dedent`
{
"extends": "../tsconfig.base.json",
"compilerOptions": {
@ -37,7 +37,7 @@ namespace ts {
],
"include": ["./index.js"]
}`,
"/src/sub-project-2/index.js": utils.dedent`
"/src/sub-project-2/index.js": Utils.dedent`
import { MyNominal } from '../sub-project/index';
const variable = {
@ -51,7 +51,7 @@ namespace ts {
return 'key';
}
`,
"/src/sub-project-2/tsconfig.json": utils.dedent`
"/src/sub-project-2/tsconfig.json": Utils.dedent`
{
"extends": "../tsconfig.base.json",
"compilerOptions": {
@ -62,7 +62,7 @@ namespace ts {
],
"include": ["./index.js"]
}`,
"/src/tsconfig.json": utils.dedent`
"/src/tsconfig.json": Utils.dedent`
{
"compilerOptions": {
"composite": true
@ -73,7 +73,7 @@ namespace ts {
],
"include": []
}`,
"/src/tsconfig.base.json": utils.dedent`
"/src/tsconfig.base.json": Utils.dedent`
{
"compilerOptions": {
"skipLibCheck": true,
@ -93,13 +93,13 @@ namespace ts {
let projFs: vfs.FileSystem;
before(() => {
projFs = loadProjectFromFiles({
"/src/common/nominal.js": utils.dedent`
"/src/common/nominal.js": Utils.dedent`
/**
* @template T, Name
* @typedef {T & {[Symbol.species]: Name}} Nominal
*/
`,
"/src/common/tsconfig.json": utils.dedent`
"/src/common/tsconfig.json": Utils.dedent`
{
"extends": "../tsconfig.base.json",
"compilerOptions": {
@ -108,13 +108,13 @@ namespace ts {
},
"include": ["nominal.js"]
}`,
"/src/sub-project/index.js": utils.dedent`
"/src/sub-project/index.js": Utils.dedent`
/**
* @typedef {Nominal<string, 'MyNominal'>} MyNominal
*/
const c = /** @type {*} */(null);
`,
"/src/sub-project/tsconfig.json": utils.dedent`
"/src/sub-project/tsconfig.json": Utils.dedent`
{
"extends": "../tsconfig.base.json",
"compilerOptions": {
@ -126,7 +126,7 @@ namespace ts {
],
"include": ["./index.js"]
}`,
"/src/sub-project-2/index.js": utils.dedent`
"/src/sub-project-2/index.js": Utils.dedent`
const variable = {
key: /** @type {MyNominal} */('value'),
};
@ -138,7 +138,7 @@ namespace ts {
return 'key';
}
`,
"/src/sub-project-2/tsconfig.json": utils.dedent`
"/src/sub-project-2/tsconfig.json": Utils.dedent`
{
"extends": "../tsconfig.base.json",
"compilerOptions": {
@ -150,7 +150,7 @@ namespace ts {
],
"include": ["./index.js"]
}`,
"/src/tsconfig.json": utils.dedent`
"/src/tsconfig.json": Utils.dedent`
{
"compilerOptions": {
"composite": true,
@ -162,7 +162,7 @@ namespace ts {
],
"include": []
}`,
"/src/tsconfig.base.json": utils.dedent`
"/src/tsconfig.base.json": Utils.dedent`
{
"compilerOptions": {
"skipLibCheck": true,
@ -200,15 +200,15 @@ namespace ts {
scenario: "javascriptProjectEmit",
subScenario: `loads js-based projects with non-moved json files and emits them correctly`,
fs: () => loadProjectFromFiles({
"/src/common/obj.json": utils.dedent`
"/src/common/obj.json": Utils.dedent`
{
"val": 42
}`,
"/src/common/index.ts": utils.dedent`
"/src/common/index.ts": Utils.dedent`
import x = require("./obj.json");
export = x;
`,
"/src/common/tsconfig.json": utils.dedent`
"/src/common/tsconfig.json": Utils.dedent`
{
"extends": "../tsconfig.base.json",
"compilerOptions": {
@ -217,12 +217,12 @@ namespace ts {
},
"include": ["index.ts", "obj.json"]
}`,
"/src/sub-project/index.js": utils.dedent`
"/src/sub-project/index.js": Utils.dedent`
import mod from '../common';
export const m = mod;
`,
"/src/sub-project/tsconfig.json": utils.dedent`
"/src/sub-project/tsconfig.json": Utils.dedent`
{
"extends": "../tsconfig.base.json",
"compilerOptions": {
@ -233,7 +233,7 @@ namespace ts {
],
"include": ["./index.js"]
}`,
"/src/sub-project-2/index.js": utils.dedent`
"/src/sub-project-2/index.js": Utils.dedent`
import { m } from '../sub-project/index';
const variable = {
@ -244,7 +244,7 @@ namespace ts {
return variable;
}
`,
"/src/sub-project-2/tsconfig.json": utils.dedent`
"/src/sub-project-2/tsconfig.json": Utils.dedent`
{
"extends": "../tsconfig.base.json",
"compilerOptions": {
@ -255,7 +255,7 @@ namespace ts {
],
"include": ["./index.js"]
}`,
"/src/tsconfig.json": utils.dedent`
"/src/tsconfig.json": Utils.dedent`
{
"compilerOptions": {
"composite": true
@ -266,7 +266,7 @@ namespace ts {
],
"include": []
}`,
"/src/tsconfig.base.json": utils.dedent`
"/src/tsconfig.base.json": Utils.dedent`
{
"compilerOptions": {
"skipLibCheck": true,

View File

@ -5,12 +5,12 @@ namespace ts {
scenario: "moduleSpecifiers",
subScenario: `synthesized module specifiers resolve correctly`,
fs: () => loadProjectFromFiles({
"/src/solution/common/nominal.ts": utils.dedent`
"/src/solution/common/nominal.ts": Utils.dedent`
export declare type Nominal<T, Name extends string> = T & {
[Symbol.species]: Name;
};
`,
"/src/solution/common/tsconfig.json": utils.dedent`
"/src/solution/common/tsconfig.json": Utils.dedent`
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
@ -18,12 +18,12 @@ namespace ts {
},
"include": ["nominal.ts"]
}`,
"/src/solution/sub-project/index.ts": utils.dedent`
"/src/solution/sub-project/index.ts": Utils.dedent`
import { Nominal } from '../common/nominal';
export type MyNominal = Nominal<string, 'MyNominal'>;
`,
"/src/solution/sub-project/tsconfig.json": utils.dedent`
"/src/solution/sub-project/tsconfig.json": Utils.dedent`
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
@ -34,7 +34,7 @@ namespace ts {
],
"include": ["./index.ts"]
}`,
"/src/solution/sub-project-2/index.ts": utils.dedent`
"/src/solution/sub-project-2/index.ts": Utils.dedent`
import { MyNominal } from '../sub-project/index';
const variable = {
@ -45,7 +45,7 @@ namespace ts {
return 'key';
}
`,
"/src/solution/sub-project-2/tsconfig.json": utils.dedent`
"/src/solution/sub-project-2/tsconfig.json": Utils.dedent`
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
@ -56,7 +56,7 @@ namespace ts {
],
"include": ["./index.ts"]
}`,
"/src/solution/tsconfig.json": utils.dedent`
"/src/solution/tsconfig.json": Utils.dedent`
{
"compilerOptions": {
"composite": true
@ -67,7 +67,7 @@ namespace ts {
],
"include": []
}`,
"/src/tsconfig.base.json": utils.dedent`
"/src/tsconfig.base.json": Utils.dedent`
{
"compilerOptions": {
"skipLibCheck": true,
@ -75,7 +75,7 @@ namespace ts {
"outDir": "lib",
}
}`,
"/src/tsconfig.json": utils.dedent`{
"/src/tsconfig.json": Utils.dedent`{
"compilerOptions": {
"composite": true
},

View File

@ -4,12 +4,12 @@ namespace ts {
scenario: "declarationEmit",
subScenario: "when same version is referenced through source and another symlinked package",
fs: () => {
const fsaPackageJson = utils.dedent`
const fsaPackageJson = Utils.dedent`
{
"name": "typescript-fsa",
"version": "3.0.0-beta-2"
}`;
const fsaIndex = utils.dedent`
const fsaIndex = Utils.dedent`
export interface Action<Payload> {
type: string;
payload: Payload;
@ -24,7 +24,7 @@ namespace ts {
export declare function actionCreatorFactory(prefix?: string | null): ActionCreatorFactory;
export default actionCreatorFactory;`;
return loadProjectFromFiles({
"/src/plugin-two/index.d.ts": utils.dedent`
"/src/plugin-two/index.d.ts": Utils.dedent`
declare const _default: {
features: {
featureOne: {
@ -48,16 +48,16 @@ namespace ts {
export default _default;`,
"/src/plugin-two/node_modules/typescript-fsa/package.json": fsaPackageJson,
"/src/plugin-two/node_modules/typescript-fsa/index.d.ts": fsaIndex,
"/src/plugin-one/tsconfig.json": utils.dedent`
"/src/plugin-one/tsconfig.json": Utils.dedent`
{
"compilerOptions": {
"target": "es5",
"declaration": true,
},
}`,
"/src/plugin-one/index.ts": utils.dedent`
"/src/plugin-one/index.ts": Utils.dedent`
import pluginTwo from "plugin-two"; // include this to add reference to symlink`,
"/src/plugin-one/action.ts": utils.dedent`
"/src/plugin-one/action.ts": Utils.dedent`
import { actionCreatorFactory } from "typescript-fsa"; // Include version of shared lib
const action = actionCreatorFactory("somekey");
const featureOne = action<{ route: string }>("feature-one");
@ -74,9 +74,9 @@ namespace ts {
scenario: "declarationEmit",
subScenario: "when pkg references sibling package through indirect symlink",
fs: () => loadProjectFromFiles({
"/src/pkg1/dist/index.d.ts": utils.dedent`
"/src/pkg1/dist/index.d.ts": Utils.dedent`
export * from './types';`,
"/src/pkg1/dist/types.d.ts": utils.dedent`
"/src/pkg1/dist/types.d.ts": Utils.dedent`
export declare type A = {
id: string;
};
@ -90,7 +90,7 @@ namespace ts {
toString(): string;
static create<T, D extends IdType = IdType>(key: string): MetadataAccessor<T, D>;
}`,
"/src/pkg1/package.json": utils.dedent`
"/src/pkg1/package.json": Utils.dedent`
{
"name": "@raymondfeng/pkg1",
"version": "1.0.0",
@ -98,11 +98,11 @@ namespace ts {
"main": "dist/index.js",
"typings": "dist/index.d.ts"
}`,
"/src/pkg2/dist/index.d.ts": utils.dedent`
"/src/pkg2/dist/index.d.ts": Utils.dedent`
export * from './types';`,
"/src/pkg2/dist/types.d.ts": utils.dedent`
"/src/pkg2/dist/types.d.ts": Utils.dedent`
export {MetadataAccessor} from '@raymondfeng/pkg1';`,
"/src/pkg2/package.json": utils.dedent`
"/src/pkg2/package.json": Utils.dedent`
{
"name": "@raymondfeng/pkg2",
"version": "1.0.0",
@ -110,12 +110,12 @@ namespace ts {
"main": "dist/index.js",
"typings": "dist/index.d.ts"
}`,
"/src/pkg3/src/index.ts": utils.dedent`
"/src/pkg3/src/index.ts": Utils.dedent`
export * from './keys';`,
"/src/pkg3/src/keys.ts": utils.dedent`
"/src/pkg3/src/keys.ts": Utils.dedent`
import {MetadataAccessor} from "@raymondfeng/pkg2";
export const ADMIN = MetadataAccessor.create<boolean>('1');`,
"/src/pkg3/tsconfig.json": utils.dedent`
"/src/pkg3/tsconfig.json": Utils.dedent`
{
"compilerOptions": {
"outDir": "dist",

View File

@ -5,7 +5,7 @@ namespace ts {
subScenario: "when passing filename for buildinfo on commandline",
fs: () => loadProjectFromFiles({
"/src/project/src/main.ts": "export const x = 10;",
"/src/project/tsconfig.json": utils.dedent`
"/src/project/tsconfig.json": Utils.dedent`
{
"compilerOptions": {
"target": "es5",
@ -25,7 +25,7 @@ namespace ts {
subScenario: "when passing rootDir from commandline",
fs: () => loadProjectFromFiles({
"/src/project/src/main.ts": "export const x = 10;",
"/src/project/tsconfig.json": utils.dedent`
"/src/project/tsconfig.json": Utils.dedent`
{
"compilerOptions": {
"incremental": true,
@ -60,7 +60,7 @@ namespace ts {
subScenario: "when passing rootDir is in the tsconfig",
fs: () => loadProjectFromFiles({
"/src/project/src/main.ts": "export const x = 10;",
"/src/project/tsconfig.json": utils.dedent`
"/src/project/tsconfig.json": Utils.dedent`
{
"compilerOptions": {
"incremental": true,

View File

@ -4,7 +4,7 @@ namespace ts {
scenario: "listFilesOnly",
subScenario: "combined with watch",
fs: () => loadProjectFromFiles({
"/src/test.ts": utils.dedent`
"/src/test.ts": Utils.dedent`
export const x = 1;`,
}),
commandLineArgs: ["/src/test.ts", "--watch", "--listFilesOnly"]
@ -14,7 +14,7 @@ namespace ts {
scenario: "listFilesOnly",
subScenario: "loose file",
fs: () => loadProjectFromFiles({
"/src/test.ts": utils.dedent`
"/src/test.ts": Utils.dedent`
export const x = 1;`,
}),
commandLineArgs: ["/src/test.ts", "--listFilesOnly"]

View File

@ -194,7 +194,7 @@ namespace ts.tscWatch {
exportedModulesMap: {},
semanticDiagnosticsPerFile: [libFilePath, file1Path, file2Path]
},
version
version: ts.version // eslint-disable-line @typescript-eslint/no-unnecessary-qualifier
})
}
],
@ -221,7 +221,7 @@ namespace ts.tscWatch {
exportedModulesMap: {},
semanticDiagnosticsPerFile: [libFilePath, file1Path, file2Path]
},
version
version: ts.version // eslint-disable-line @typescript-eslint/no-unnecessary-qualifier
})
}
],
@ -285,7 +285,7 @@ namespace ts.tscWatch {
file2ReuasableError
]
},
version
version: ts.version // eslint-disable-line @typescript-eslint/no-unnecessary-qualifier
})
}
],
@ -315,7 +315,7 @@ namespace ts.tscWatch {
file2ReuasableError
]
},
version
version: ts.version // eslint-disable-line @typescript-eslint/no-unnecessary-qualifier
})
}
],
@ -348,7 +348,7 @@ namespace ts.tscWatch {
]
},
},
version
version: ts.version // eslint-disable-line @typescript-eslint/no-unnecessary-qualifier
})
}
],
@ -420,7 +420,7 @@ namespace ts.tscWatch {
exportedModulesMap: {},
semanticDiagnosticsPerFile: [libFilePath, file1Path, file2Path]
},
version
version: ts.version // eslint-disable-line @typescript-eslint/no-unnecessary-qualifier
})
}
],
@ -446,7 +446,7 @@ namespace ts.tscWatch {
exportedModulesMap: {},
semanticDiagnosticsPerFile: [libFilePath, file1Path, file2Path]
},
version
version: ts.version // eslint-disable-line @typescript-eslint/no-unnecessary-qualifier
})
}
],
@ -506,7 +506,7 @@ namespace ts.tscWatch {
file2ReuasableError
]
},
version
version: ts.version // eslint-disable-line @typescript-eslint/no-unnecessary-qualifier
})
}
],
@ -536,7 +536,7 @@ namespace ts.tscWatch {
file1Path
]
},
version
version: ts.version // eslint-disable-line @typescript-eslint/no-unnecessary-qualifier
})
}
],
@ -620,7 +620,7 @@ namespace ts.tscWatch {
]
},
},
version
version: ts.version // eslint-disable-line @typescript-eslint/no-unnecessary-qualifier
})
}
],
@ -729,7 +729,7 @@ export { C } from "./c";
path: `${project}/tsconfig.tsbuildinfo`,
content: getBuildInfoText({
program: initialProgram,
version
version: ts.version // eslint-disable-line @typescript-eslint/no-unnecessary-qualifier
})
}
],
@ -753,7 +753,7 @@ export { C } from "./c";
},
...rest
},
version
version: ts.version // eslint-disable-line @typescript-eslint/no-unnecessary-qualifier
})
}
],

View File

@ -276,7 +276,7 @@ namespace ts.projectSystem {
configFileName: "tsconfig.json",
projectType: "configured",
languageServiceEnabled: true,
version,
version: ts.version, // eslint-disable-line @typescript-eslint/no-unnecessary-qualifier
...partial,
});
}

View File

@ -1,6 +1,6 @@
const expect: typeof _chai.expect = _chai.expect;
namespace ts.server {
const _chai: typeof import("chai") = require("chai");
const expect: typeof _chai.expect = _chai.expect;
let lastWrittenToHost: string;
const noopFileWatcher: FileWatcher = { close: noop };
const mockHost: ServerHost = {
@ -179,7 +179,9 @@ namespace ts.server {
type: "request"
};
const expected: protocol.StatusResponseBody = { version };
const expected: protocol.StatusResponseBody = {
version: ts.version, // eslint-disable-line @typescript-eslint/no-unnecessary-qualifier
};
assert.deepEqual(session.executeCommand(req).response, expected);
});
});

View File

@ -0,0 +1,2 @@
// empty ref to Utils so it can be referenced by unittests
namespace Utils {}

2
src/testRunner/vfsRef.ts Normal file
View File

@ -0,0 +1,2 @@
// empty ref to vfs so it can be referenced by unittests
namespace vfs {}

View File

@ -0,0 +1,2 @@
// empty ref to vpath so it can be referenced by unittests
namespace vpath {}

View File

@ -5876,7 +5876,6 @@ declare namespace ts {
function getDefaultCompilerOptions(): CompilerOptions;
function getSupportedCodeFixes(): string[];
function createLanguageServiceSourceFile(fileName: string, scriptSnapshot: IScriptSnapshot, scriptTarget: ScriptTarget, version: string, setNodeParents: boolean, scriptKind?: ScriptKind): SourceFile;
let disableIncrementalParsing: boolean;
function updateLanguageServiceSourceFile(sourceFile: SourceFile, scriptSnapshot: IScriptSnapshot, version: string, textChangeRange: TextChangeRange | undefined, aggressiveChecks?: boolean): SourceFile;
function createLanguageService(host: LanguageServiceHost, documentRegistry?: DocumentRegistry, syntaxOnly?: boolean): LanguageService;
/**

View File

@ -5876,7 +5876,6 @@ declare namespace ts {
function getDefaultCompilerOptions(): CompilerOptions;
function getSupportedCodeFixes(): string[];
function createLanguageServiceSourceFile(fileName: string, scriptSnapshot: IScriptSnapshot, scriptTarget: ScriptTarget, version: string, setNodeParents: boolean, scriptKind?: ScriptKind): SourceFile;
let disableIncrementalParsing: boolean;
function updateLanguageServiceSourceFile(sourceFile: SourceFile, scriptSnapshot: IScriptSnapshot, version: string, textChangeRange: TextChangeRange | undefined, aggressiveChecks?: boolean): SourceFile;
function createLanguageService(host: LanguageServiceHost, documentRegistry?: DocumentRegistry, syntaxOnly?: boolean): LanguageService;
/**