[WIP] harness rewrite

This commit is contained in:
Ron Buckton
2017-06-30 21:14:47 -07:00
parent 6370fc8b85
commit 21e3f6812c
56 changed files with 4945 additions and 125 deletions
+3 -1
View File
@@ -57,4 +57,6 @@ internal/
!tests/cases/projects/NodeModulesSearch/**/*
!tests/baselines/reference/project/nodeModules*/**/*
.idea
yarn.lock
yarn.lock
package-lock.json
.failed-tests
+8
View File
@@ -18,6 +18,14 @@
"problemMatcher": [
"$tsc"
]
},
{
"taskName": "harness",
"isBuildCommand": true,
"showOutput": "silent",
"problemMatcher": [
"$tsc"
]
}
]
}
+45 -6
View File
@@ -38,7 +38,7 @@ const {runTestsInParallel} = mochaParallel;
Error.stackTraceLimit = 1000;
const cmdLineOptions = minimist(process.argv.slice(2), {
boolean: ["debug", "inspect", "light", "colors", "lint", "soft"],
boolean: ["debug", "inspect", "light", "colors", "lint", "soft", "failed", "bail", "keepFailed"],
string: ["browser", "tests", "host", "reporter", "stackTraceLimit", "timeout"],
alias: {
b: "browser",
@@ -49,6 +49,7 @@ const cmdLineOptions = minimist(process.argv.slice(2), {
c: "colors", color: "colors",
f: "files", file: "files",
w: "workers",
"keep-failed": "keepFailed"
},
default: {
soft: false,
@@ -57,9 +58,12 @@ const cmdLineOptions = minimist(process.argv.slice(2), {
inspect: process.env.inspect || process.env["inspect-brk"] || process.env.i,
host: process.env.TYPESCRIPT_HOST || process.env.host || "node",
browser: process.env.browser || process.env.b || "IE",
bail: process.env.bail || false,
timeout: process.env.timeout || 40000,
tests: process.env.test || process.env.tests || process.env.t,
light: process.env.light || false,
failed: process.env.failed || false,
keepFailed: process.env.keepFailed || process.env["keep-failed"] || false,
reporter: process.env.reporter || process.env.r,
lint: process.env.lint || true,
files: process.env.f || process.env.file || process.env.files || "",
@@ -93,6 +97,7 @@ const docDirectory = "doc/";
const builtDirectory = "built/";
const builtLocalDirectory = "built/local/";
const builtHarnessDirectory = "built/harness/";
const LKGDirectory = "lib/";
const copyright = "CopyrightNotice.txt";
@@ -565,6 +570,23 @@ gulp.task(run, /*help*/ false, [servicesFile], () => {
.pipe(gulp.dest("src/harness"));
});
const run2 = path.join(builtHarnessDirectory, "run.js");
gulp.task(run2, /*help*/ false, [], () => {
const harnessProject = tsc.createProject("src/harness2/tsconfig.json", getCompilerSettings({
types: ["node", "mocha", "chai"],
lib: ["es6"],
strict: true
}, /*useBuiltCompiler*/ false));
return harnessProject.src()
.pipe(newer(builtHarnessDirectory))
.pipe(sourcemaps.init())
.pipe(harnessProject())
.pipe(sourcemaps.write(".", <any>{ includeContent: false, destPath: builtHarnessDirectory }))
.pipe(gulp.dest(builtHarnessDirectory));
});
gulp.task("harness", "Builds the test harness", [run2]);
const internalTests = "internal/";
const localBaseline = "tests/baselines/local/";
@@ -594,7 +616,7 @@ function restoreSavedNodeEnv() {
process.env.NODE_ENV = savedNodeEnv;
}
function runConsoleTests(defaultReporter: string, runInParallel: boolean, done: (e?: any) => void) {
function runConsoleTests(defaultReporter: string, runInParallel: boolean, run: string, done: (e?: any) => void) {
const lintFlag = cmdLineOptions["lint"];
cleanTestDirs((err) => {
if (err) { console.error(err); failWithStatus(err, 1); }
@@ -603,6 +625,9 @@ function runConsoleTests(defaultReporter: string, runInParallel: boolean, done:
const inspect = cmdLineOptions["inspect"];
const tests = cmdLineOptions["tests"];
const light = cmdLineOptions["light"];
const bail = cmdLineOptions["bail"];
const failed = cmdLineOptions["failed"];
const keepFailed = cmdLineOptions["keepFailed"];
const stackTraceLimit = cmdLineOptions["stackTraceLimit"];
const testConfigFile = "test.config";
if (fs.existsSync(testConfigFile)) {
@@ -637,16 +662,23 @@ function runConsoleTests(defaultReporter: string, runInParallel: boolean, done:
// default timeout is 2sec which really should be enough, but maybe we just need a small amount longer
if (!runInParallel) {
const args = [];
args.push("-R", reporter);
args.push("-R", "scripts/mocha-file-reporter");
args.push("-O", '"reporter=' + reporter + (keepFailed ? ",keepFailed=true" : "") + '"');
if (tests) {
args.push("-g", `"${tests}"`);
}
else if (failed) {
args.push("--opts", ".failed-tests");
}
if (colors) {
args.push("--colors");
}
else {
args.push("--no-colors");
}
if (bail) {
args.push("--bail");
}
if (inspect) {
args.unshift("--inspect-brk");
}
@@ -675,7 +707,7 @@ function runConsoleTests(defaultReporter: string, runInParallel: boolean, done:
}
args.push(run);
setNodeEnvToDevelopment();
runTestsInParallel(taskConfigsFolder, run, { testTimeout: testTimeout, noColors: colors === " --no-colors " }, function(err) {
runTestsInParallel(taskConfigsFolder, run, { testTimeout: testTimeout, noColors: colors === " --no-colors ", keepFailed }, function(err) {
// last worker clean everything and runs linter in case if there were no errors
del(taskConfigsFolder).then(() => {
if (!err) {
@@ -720,13 +752,20 @@ function runConsoleTests(defaultReporter: string, runInParallel: boolean, done:
}
gulp.task("runtests-parallel", "Runs all the tests in parallel using the built run.js file. Optional arguments are: --t[ests]=category1|category2|... --d[ebug]=true.", ["build-rules", "tests"], (done) => {
runConsoleTests("min", /*runInParallel*/ true, done);
runConsoleTests("min", /*runInParallel*/ true, run, done);
});
gulp.task("runtests",
"Runs the tests using the built run.js file. Optional arguments are: --t[ests]=regex --r[eporter]=[list|spec|json|<more>] --d[ebug]=true --color[s]=false --lint=true.",
["build-rules", "tests"],
(done) => {
runConsoleTests("mocha-fivemat-progress-reporter", /*runInParallel*/ false, done);
runConsoleTests("mocha-fivemat-progress-reporter", /*runInParallel*/ false, run, done);
});
gulp.task("runtests2",
"Runs the tests using the built run.js file. Optional arguments are: --t[ests]=regex --r[eporter]=[list|spec|json|<more>] --d[ebug]=true --color[s]=false --lint=true.",
["harness"],
(done) => {
runConsoleTests("mocha-fivemat-progress-reporter", /*runInParallel*/ false, run2, done);
});
const nodeServerOutFile = "tests/webTestServer.js";
+49 -6
View File
@@ -20,6 +20,7 @@ var docDirectory = "doc/";
var builtDirectory = "built/";
var builtLocalDirectory = "built/local/";
var builtHarnessDirectory = "built/harness/";
var LKGDirectory = "lib/";
var copyright = "CopyrightNotice.txt";
@@ -87,6 +88,7 @@ var typingsInstallerSources = filesFromConfig(path.join(serverDirectory, "typing
var watchGuardSources = filesFromConfig(path.join(serverDirectory, "watchGuard/tsconfig.json"));
var serverSources = filesFromConfig(path.join(serverDirectory, "tsconfig.json"))
var languageServiceLibrarySources = filesFromConfig(path.join(serverDirectory, "tsconfig.library.json"));
var harness2Sources = filesFromConfig("./src/harness2/tsconfig.json");
var harnessCoreSources = [
"harness.ts",
@@ -276,6 +278,7 @@ var builtLocalCompiler = path.join(builtLocalDirectory, compilerFilename);
* @param {boolean} opts.stripInternal: true if compiler should remove declarations marked as @internal
* @param {boolean} opts.inlineSourceMap: true if compiler should inline sourceMap
* @param {Array} opts.types: array of types to include in compilation
* @param {boolean} opts.strict: true to compile using --strict
* @param callback: a function to execute after the compilation process ends
*/
function compileFile(outFile, sources, prereqs, prefixes, useBuiltCompiler, opts, callback) {
@@ -342,6 +345,9 @@ function compileFile(outFile, sources, prereqs, prefixes, useBuiltCompiler, opts
else {
options += " --lib es5"
}
if (opts.strict) {
options += " --strict";
}
options += " --noUnusedLocals --noUnusedParameters";
var cmd = host + " " + compilerPath + " " + options + " ";
@@ -715,6 +721,7 @@ task("LKG", ["clean", "release", "local"].concat(libraryTargets), function () {
// Test directory
directory(builtLocalDirectory);
directory(builtHarnessDirectory);
// Task to build the tests infrastructure using the built compiler
var run = path.join(builtLocalDirectory, "run.js");
@@ -726,6 +733,15 @@ compileFile(
/*useBuiltCompiler:*/ true,
/*opts*/ { inlineSourceMap: true, types: ["node", "mocha", "chai"], lib: "es6" });
var run2 = path.join(builtHarnessDirectory, "run.js");
compileFile(
/*outFile*/ run2,
/*source*/ harness2Sources,
/*prereqs*/[builtHarnessDirectory].concat(harness2Sources),
/*prefixes*/[],
/*useBuiltCompiler:*/ false,
/*opts*/ { inlineSourceMap: true, types: ["node", "mocha", "chai"], lib: "es6", noOutFile: true, outDir: builtHarnessDirectory, strict: true });
var internalTests = "internal/";
var localBaseline = "tests/baselines/local/";
@@ -737,6 +753,9 @@ var refRwcBaseline = path.join(internalTests, "baselines/rwc/reference");
var localTest262Baseline = path.join(internalTests, "baselines/test262/local");
var refTest262Baseline = path.join(internalTests, "baselines/test262/reference");
desc("Builds the test harness");
task("harness", [run2]);
desc("Builds the test infrastructure using the built compiler");
task("tests", ["local", run].concat(libraryTargets));
@@ -801,7 +820,7 @@ function deleteTemporaryProjectOutput() {
}
}
function runConsoleTests(defaultReporter, runInParallel) {
function runConsoleTests(defaultReporter, runInParallel, run) {
var dirty = process.env.dirty;
if (!dirty) {
cleanTestDirs();
@@ -811,6 +830,8 @@ function runConsoleTests(defaultReporter, runInParallel) {
var inspect = process.env.inspect || process.env["inspect-brk"] || process.env.i;
var testTimeout = process.env.timeout || defaultTestTimeout;
var tests = process.env.test || process.env.tests || process.env.t;
var failed = process.env.failed || false;
var keepFailed = process.env.keepFailed || process.env["keep-failed"] || false;
var light = process.env.light || false;
var stackTraceLimit = process.env.stackTraceLimit;
var testConfigFile = 'test.config';
@@ -847,12 +868,26 @@ function runConsoleTests(defaultReporter, runInParallel) {
// timeout normally isn't necessary but Travis-CI has been timing out on compiler baselines occasionally
// default timeout is 2sec which really should be enough, but maybe we just need a small amount longer
if (!runInParallel) {
if (!tests && process.env.failed && process.env.failed.toLowerCase() === "true") {
const unique = Object.create(null);
tests = fs.readdirSync("tests/baselines/local")
.map(name => name
.replace(/(\.errors\.txt|\.jsx?(\.map)?|\.sourcemap\.txt|\.types|\.symbols)(\.delete)?$/, ".")
.replace(/[^\w\s\\-]/g, m => "\\" + m))
.filter(name => unique[name] ? false : unique[name] = true)
.join("|");
}
var startTime = mark();
var args = [];
args.push("-R", reporter);
args.push("-R", "scripts/mocha-file-reporter");
args.push("-O", '"reporter=' + reporter + (keepFailed ? ",keepFailed=true" : "") + '"');
if (tests) {
args.push("-g", `"${tests}"`);
}
else if (failed) {
args.push("--opts", ".failed-tests");
}
if (colors) {
args.push("--colors");
}
@@ -894,12 +929,12 @@ function runConsoleTests(defaultReporter, runInParallel) {
var savedNodeEnv = process.env.NODE_ENV;
process.env.NODE_ENV = "development";
var startTime = mark();
runTestsInParallel(taskConfigsFolder, run, { testTimeout: testTimeout, noColors: !colors }, function (err) {
runTestsInParallel(taskConfigsFolder, run, { testTimeout: testTimeout, noColors: !colors, keepFailed }, function (err) {
process.env.NODE_ENV = savedNodeEnv;
measure(startTime);
// last worker clean everything and runs linter in case if there were no errors
deleteTemporaryProjectOutput();
jake.rmRf(taskConfigsFolder);
//jake.rmRf(taskConfigsFolder);
if (err) {
fail(err);
}
@@ -937,12 +972,20 @@ function runConsoleTests(defaultReporter, runInParallel) {
desc("Runs all the tests in parallel using the built run.js file. Optional arguments are: t[ests]=category1|category2|... d[ebug]=true.");
task("runtests-parallel", ["build-rules", "tests", builtLocalDirectory], function () {
runConsoleTests('min', /*runInParallel*/ true);
runConsoleTests('min', /*runInParallel*/ true, run);
}, { async: true });
desc("Runs the tests using the built run.js file. Optional arguments are: t[ests]=regex r[eporter]=[list|spec|json|<more>] d[ebug]=true color[s]=false lint=true bail=false dirty=false.");
task("runtests", ["build-rules", "tests", builtLocalDirectory], function() {
runConsoleTests('mocha-fivemat-progress-reporter', /*runInParallel*/ false);
runConsoleTests('mocha-fivemat-progress-reporter', /*runInParallel*/ false, run);
}, { async: true });
task("runtests2", [run2], function () {
runConsoleTests('dot', /*runInParallel*/ false, run2);
}, { async: true });
task("runtests-parallel2", [run2], function () {
runConsoleTests('min', /*runInParallel*/ true, run2);
}, { async: true });
desc("Generates code coverage data via instanbul");
+1
View File
@@ -47,6 +47,7 @@
"@types/node": "latest",
"@types/q": "latest",
"@types/run-sequence": "latest",
"@types/source-map-support": "^0.4.0",
"@types/through2": "latest",
"browserify": "latest",
"chai": "latest",
+80
View File
@@ -0,0 +1,80 @@
var Mocha = require('mocha');
var path = require('path');
var fs = require('fs');
exports = module.exports = FileReporter;
function FileReporter(runner, options) {
if (!runner) return;
options = options || {};
var reporterOptions = this.reporterOptions = options.reporterOptions || {};
reporterOptions.file = reporterOptions.file || ".failed-tests";
reporterOptions.keepFailed = reporterOptions.keepFailed || false;
if (reporterOptions.reporter) {
var _reporter;
if (typeof reporterOptions.reporter === "function") {
_reporter = reporterOptions.reporter;
}
else if (Mocha.reporters[reporterOptions.reporter]) {
_reporter = Mocha.reporters[reporterOptions.reporter];
}
else {
try {
_reporter = require(reporterOptions.reporter);
}
catch (err) {
_reporter = require(path.resolve(process.cwd(), reporterOptions.reporter));
}
}
var newOptions = {};
for (var p in options) newOptions[p] = options[p];
newOptions.reporterOptions = reporterOptions.reporterOptions || {};
this.reporter = new _reporter(runner, newOptions);
}
var failures = this.failures = [];
var tests = this.tests = [];
runner.on('test end', function (test) {
tests.push(test);
})
runner.on('fail', function (test, err) {
failures.push(test);
});
}
FileReporter.prototype.done = function (numFailures, fn) {
FileReporter.writeFailures(this.reporterOptions.file, this.failures, this.reporterOptions.keepFailed || this.tests.length === 0, done);
function done(err) {
var reporter = this.reporter;
if (reporter && reporter.done) {
reporter.done(numFailures, fn);
}
else {
if (fn) fn(numFailures);
}
if (err) console.error(err);
}
}
FileReporter.writeFailures = function (fileName, failures, keepFailed, fn) {
if (keepFailed) {
fn();
return;
}
if (failures.length) {
var failed = failures.map(function (test) { return escapeRegExp(test.fullTitle()); }).join("|");
fs.writeFile(fileName, "--grep " + failed, "utf8", fn);
}
else {
fs.unlink(fileName, function () { fn(); });
}
};
var reservedCharacterRegExp = /[^\w]/g;
function escapeRegExp(pattern) {
return pattern.replace(reservedCharacterRegExp, function (match) { return "\\" + match; });
}
+98 -49
View File
@@ -1,14 +1,15 @@
var tty = require("tty")
, readline = require("readline")
, fs = require("fs")
, path = require("path")
, child_process = require("child_process")
, os = require("os")
, mocha = require("mocha")
, Base = mocha.reporters.Base
, color = Base.color
, cursor = Base.cursor
, ms = require("mocha/lib/ms");
, readline = require("readline")
, fs = require("fs")
, path = require("path")
, child_process = require("child_process")
, os = require("os")
, mocha = require("mocha")
, Base = mocha.reporters.Base
, color = Base.color
, cursor = Base.cursor
, ms = require("mocha/lib/ms")
, FileReporter = require("./mocha-file-reporter");
var isatty = tty.isatty(1) && tty.isatty(2);
var tapRangePattern = /^(\d+)\.\.(\d+)(?:$|\r\n?|\n)/;
@@ -19,7 +20,7 @@ exports.runTestsInParallel = runTestsInParallel;
exports.ProgressBars = ProgressBars;
function runTestsInParallel(taskConfigsFolder, run, options, cb) {
if (options === undefined) options = { };
if (options === undefined) options = {};
return discoverTests(run, options, function (error) {
if (error) {
@@ -33,7 +34,12 @@ function runTestsInParallel(taskConfigsFolder, run, options, cb) {
function discoverTests(run, options, cb) {
console.log("Discovering tests...");
var cmd = "mocha -R " + require.resolve("./mocha-none-reporter.js") + " " + run;
var args = [];
args.push("--no-timeouts");
args.push("--delay"); // prevents mocha from running tests.
args.push(run);
args.push("--discover"); // signal discovery (must come after files)
var cmd = "mocha " + args.join(" ");
var p = spawnProcess(cmd);
p.on("exit", function (status) {
if (status) {
@@ -46,25 +52,19 @@ function discoverTests(run, options, cb) {
}
function runTests(taskConfigsFolder, run, options, cb) {
var configFiles = fs.readdirSync(taskConfigsFolder);
var numPartitions = configFiles.length;
if (numPartitions <= 0) {
cb();
return;
}
console.log("Running tests on " + numPartitions + " threads...");
var partitions = Array(numPartitions);
var progressBars = new ProgressBars();
progressBars.enable();
var counter = numPartitions;
configFiles.forEach(runTestsInPartition);
function runTestsInPartition(file, index) {
var partition = {
file: path.join(taskConfigsFolder, file),
var partitions = fs.readdirSync(taskConfigsFolder).map(function (file, index) {
var file = path.join(taskConfigsFolder, file);
var args = [];
args.push("-t", options.testTimeout || 40000);
args.push("-R", "tap");
args.push("--no-colors");
args.push(run);
args.push("--config='" + file + "'");
var cmd = "mocha " + args.join(" ");
return {
file: file,
cmd: cmd,
index: index,
tests: 0,
passed: 0,
failed: 0,
@@ -72,23 +72,48 @@ function runTests(taskConfigsFolder, run, options, cb) {
current: undefined,
start: undefined,
end: undefined,
catastrophicError: "",
failures: []
};
partitions[index] = partition;
});
if (partitions.length <= 0) {
cb();
return;
}
console.log("Running tests on " + partitions.length + " threads...");
var progressBars = new ProgressBars();
progressBars.enable();
var counter = partitions.length;
partitions.forEach(runTestsInPartition);
function runTestsInPartition(partition) {
// Set up the progress bar.
updateProgress(0);
// Start the background process.
var cmd = "mocha -t " + (options.testTimeout || 20000) + " -R tap --no-colors " + run + " --config='" + partition.file + "'";
var p = spawnProcess(cmd);
var p = spawnProcess(partition.cmd);
var rl = readline.createInterface({
input: p.stdout,
terminal: false
});
var rlError = readline.createInterface({
input: p.stderr,
terminal: false
});
rl.on("line", onmessage);
rlError.on("line", onErrorMessage);
p.on("exit", onexit)
function onErrorMessage(line) {
partition.catastrophicError += line + os.EOL;
}
function onmessage(line) {
if (partition.start === undefined) {
partition.start = Date.now();
@@ -153,15 +178,17 @@ function runTests(taskConfigsFolder, run, options, cb) {
}
}
function onexit() {
function onexit(code) {
if (partition.end === undefined) {
partition.end = Date.now();
}
partition.duration = partition.end - partition.start;
var summaryColor = partition.failed ? "fail" : "green";
var summarySymbol = partition.failed ? Base.symbols.err : Base.symbols.ok;
var summaryTests = (partition.passed === partition.tests ? partition.passed : partition.passed + "/" + partition.tests) + " passing";
var isPartitionFail = partition.failed || code !== 0;
var summaryColor = isPartitionFail ? "fail" : "green";
var summarySymbol = isPartitionFail ? Base.symbols.err : Base.symbols.ok;
var summaryTests = (isPartitionFail ? partition.passed + "/" + partition.tests : partition.passed) + " passing";
var summaryDuration = "(" + ms(partition.duration) + ")";
var savedUseColors = Base.useColors;
Base.useColors = !options.noColors;
@@ -181,7 +208,7 @@ function runTests(taskConfigsFolder, run, options, cb) {
}
progressBars.update(
index,
partition.index,
percentComplete,
progressColor,
title
@@ -198,12 +225,34 @@ function runTests(taskConfigsFolder, run, options, cb) {
failures = reporter.failures;
var duration = 0;
for (var i = 0; i < numPartitions; i++) {
var catastrophicError = "";
for (var i = 0; i < partitions.length; i++) {
var partition = partitions[i];
stats.passes += partition.passed;
stats.failures += partition.failed;
stats.tests += partition.tests;
duration += partition.duration;
if (partition.catastrophicError !== "") {
// Partition is written out to a temporary file as a JSON object.
// Below is an example of how the partition JSON object looks like
// {
// "light":false,
// "tasks":[
// {
// "runner":"compiler",
// "files":["tests/cases/compiler/es6ImportNamedImportParsingError.ts"]
// }
// ],
// "runUnitTests":false
// }
var jsonText = fs.readFileSync(partition.file);
var configObj = JSON.parse(jsonText);
if (configObj.tasks && configObj.tasks[0]) {
catastrophicError += "Error from one or more of these files: " + configObj.tasks[0].files + os.EOL;
catastrophicError += partition.catastrophicError;
catastrophicError += os.EOL;
}
}
for (var j = 0; j < partition.failures.length; j++) {
var failure = partition.failures[j];
failures.push(makeMochaTest(failure));
@@ -223,18 +272,18 @@ function runTests(taskConfigsFolder, run, options, cb) {
reporter.epilogue();
}
if (stats.failures) {
return cb(new Error("Test failures reported: " + stats.failures));
}
else {
FileReporter.writeFailures(".failed-tests", failures, options.keepFailed, function (err) {
if (err) return cb(err);
if (catastrophicError !== "") return cb(new Error(catastrophicError));
if (stats.failures) return cb(new Error("Test failures reported: " + stats.failures));
return cb();
}
});
}
}
function makeMochaTest(test) {
return {
fullTitle: function() {
fullTitle: function () {
return test.name;
},
err: {
@@ -247,9 +296,9 @@ function runTests(taskConfigsFolder, run, options, cb) {
var nodeModulesPathPrefix = path.resolve("./node_modules/.bin/") + path.delimiter;
if (process.env.path !== undefined) {
process.env.path = nodeModulesPathPrefix + process.env.path;
process.env.path = nodeModulesPathPrefix + process.env.path;
} else if (process.env.PATH !== undefined) {
process.env.PATH = nodeModulesPathPrefix + process.env.PATH;
process.env.PATH = nodeModulesPathPrefix + process.env.PATH;
}
function spawnProcess(cmd, options) {
@@ -294,7 +343,7 @@ ProgressBars.prototype = {
update: function (index, percentComplete, color, title) {
percentComplete = minMax(percentComplete, 0, 1);
var progressBar = this._progressBars[index] || (this._progressBars[index] = { });
var progressBar = this._progressBars[index] || (this._progressBars[index] = {});
var width = this._options.width;
var n = Math.floor(width * percentComplete);
var i = width - n;
+1 -1
View File
@@ -2581,7 +2581,7 @@ namespace ts {
// JavaScript files are always LanguageVariant.JSX, as JSX syntax is allowed in .js files also.
// So for JavaScript files, '.jsx' is only emitted if the input was '.jsx', and JsxEmit.Preserve.
// For TypeScript, the only time to emit with a '.jsx' extension, is on JSX input, and JsxEmit.Preserve
function getOutputExtension(sourceFile: SourceFile, options: CompilerOptions): Extension {
export function getOutputExtension(sourceFile: SourceFile, options: CompilerOptions): Extension {
if (options.jsx === JsxEmit.Preserve) {
if (isSourceFileJavaScript(sourceFile)) {
if (fileExtensionIs(sourceFile.fileName, Extension.Jsx)) {
+215
View File
@@ -0,0 +1,215 @@
// This file contains the minimal definitions for TypeScript needed to host the
// compiler for the purpose of running tests.
// Re-export the built compiler using a CommonJS-style export. This is necessary as we
// want 'api' to masquerade as a minimal surface for the built compiler without
// taking a compile-time dependency to avoid long compilation times.
module.exports = require("../../built/local/typescript.js");
export interface MapLike<T> {
[index: string]: T | undefined;
}
export interface DiagnosticMessageChain {
messageText: string;
category: number;
code: number;
next?: DiagnosticMessageChain;
}
export interface Diagnostic {
file: SourceFile | undefined;
start: number | undefined;
length: number | undefined;
messageText: string | DiagnosticMessageChain;
category: number;
code: number;
source?: string;
}
export interface PluginImport {
name: string;
}
export interface CommandLineOptionBase {
name: string;
type: "string" | "number" | "boolean" | "object" | "list" | Map<string, number | string>;
}
export interface CommandLineOptionOfPrimitiveType extends CommandLineOptionBase {
type: "string" | "number" | "boolean";
}
export interface CommandLineOptionOfCustomType extends CommandLineOptionBase {
type: Map<string, number | string>; // an object literal mapping named values to actual values
}
export interface CommandLineOptionOfListType extends CommandLineOptionBase {
type: "list";
element: CommandLineOptionOfCustomType | CommandLineOptionOfPrimitiveType;
}
export type CommandLineOption = CommandLineOptionOfCustomType | CommandLineOptionOfPrimitiveType | CommandLineOptionOfListType;
export type CompilerOptionsValue = string | number | boolean | (string | number)[] | string[] | MapLike<string[]> | PluginImport[];
export interface CompilerOptions {
declaration?: boolean;
declarationDir?: string;
inlineSourceMap?: boolean;
mapRoot?: string;
newLine?: NewLineKind;
noEmitOnError?: boolean;
noEmit?: boolean;
noErrorTruncation?: boolean;
out?: string;
outDir?: string;
outFile?: string;
skipDefaultLibCheck?: boolean;
sourceMap?: boolean;
target?: ScriptTarget;
traceResoluton?: boolean;
[option: string]: CompilerOptionsValue | undefined;
}
export interface ParsedCommandLine {
options: CompilerOptions;
fileNames: string[];
errors: Diagnostic[];
}
export interface SourceMapSpan {
emittedLine: number;
emittedColumn: number;
sourceLine: number;
sourceColumn: number;
sourceIndex: number;
nameIndex?: number;
}
export interface SourceMapData {
jsSourceMappingURL: string;
inputSourceFileNames: string[];
sourceMapFilePath: string;
sourceMapFile: string;
sourceMapSourceRoot: string;
sourceMapSources: string[];
sourceMapSourcesContent?: string[];
sourceMapNames?: string[];
sourceMapMappings: string;
sourceMapDecodedMappings: SourceMapSpan[];
}
export interface EmitResult {
emitSkipped: boolean;
diagnostics: Diagnostic[];
emittedFiles: string[];
sourceMaps: SourceMapData[];
}
export interface ModuleResolutionHost {
fileExists(fileName: string): boolean;
readFile(fileName: string): string | undefined;
trace?(s: string): void;
directoryExists?(directoryName: string): boolean;
realpath?(path: string): string;
getCurrentDirectory?(): string;
getDirectories?(path: string): string[];
}
export interface ScriptReferenceHost {
getCompilerOptions(): CompilerOptions;
getSourceFile(fileName: string): SourceFile | undefined;
getSourceFileByPath(path: string): SourceFile | undefined;
getCurrentDirectory(): string;
}
export interface ParseConfigHost {
useCaseSensitiveFileNames: boolean;
readDirectory(rootDir: string, extensions: string[], excludes: string[], includes: string[]): string[];
fileExists(path: string): boolean;
readFile(path: string): string | undefined;
}
export interface CompilerHost extends ModuleResolutionHost {
getSourceFile(fileName: string, languageVersion: number, onError?: (message: string) => void): SourceFile | undefined;
getDefaultLibFileName(options: CompilerOptions): string;
getDefaultLibLocation(): string;
writeFile(fileName: string, data: string, writeByteOrderMark: boolean, onError?: (message: string) => void, sourceFiles?: SourceFile[]): void;
getCurrentDirectory(): string;
getDirectories(path: string): string[];
getCanonicalFileName(fileName: string): string;
useCaseSensitiveFileNames(): boolean;
getNewLine(): string;
}
export interface Program extends ScriptReferenceHost {
getRootFileNames(): string[];
getSourceFiles(): SourceFile[];
emit(): EmitResult;
getOptionsDiagnostics(): Diagnostic[];
getGlobalDiagnostics(): Diagnostic[];
getSyntacticDiagnostics(): Diagnostic[];
getSemanticDiagnostics(): Diagnostic[];
getDeclarationDiagnostics(): Diagnostic[];
// getTypeChecker(): TypeChecker;
getCommonSourceDirectory(): string;
}
export interface SourceFile {
fileName: string;
text: string;
}
export interface TypesAndSymbols {
line: number;
text: string;
type: string | undefined;
symbol: string | undefined;
declarations: { fileName: string, line: number, character: number }[] | undefined;
}
export interface FormatDiagnosticsHost {
getCurrentDirectory(): string;
getCanonicalFileName(fileName: string): string;
getNewLine(): string;
}
export declare enum DiagnosticCategory {
}
export declare enum ScriptTarget {
ES3,
ES5,
ES2015,
ES2016,
ES2017,
ESNext
}
export declare enum NewLineKind {
CarriageReturnLineFeed
}
export declare namespace Debug {
const isDebugging: boolean;
function enableDebugInfo(): void;
}
export declare const optionDeclarations: CommandLineOption[];
export declare function compareDiagnostics(d1: Diagnostic, d2: Diagnostic): number;
export declare function fixupCompilerOptions(options: CompilerOptions, diagnostics: Diagnostic[]): CompilerOptions;
export declare function flattenDiagnosticMessageText(messageText: string | DiagnosticMessageChain, newLine: string): string;
export declare function formatDiagnostics(diagnostics: Diagnostic[], host: FormatDiagnosticsHost): string;
export declare function getDefaultLibFileName(options: CompilerOptions): string;
export declare function parseConfigFileTextToJson(fileName: string, jsonText: string, stripComments?: boolean): { config?: any; error?: Diagnostic };
export declare function parseJsonConfigFileContent(json: any, host: ParseConfigHost, basePath: string, existingOptions?: CompilerOptions, configFileName?: string): ParsedCommandLine;
export declare function parseListTypeOption(opt: CommandLineOptionOfListType, value: string | undefined, errors: Diagnostic[]): (string | number)[] | undefined;
export declare function parseCustomTypeOption(opt: CommandLineOptionOfCustomType, value: string, errors: Diagnostic[]): string | number | undefined;
export declare function createProgram(rootNames: string[], options: CompilerOptions, host?: CompilerHost, oldProgram?: Program): Program;
export declare function getPreEmitDiagnostics(program: Program): Diagnostic[];
export declare function createSourceFile(fileName: string, sourceText: string, languageVersion: number, setParentNodes?: boolean): SourceFile;
export declare function matchFiles(path: string, extensions: string[], excludes: string[], includes: string[], useCaseSensitiveFileNames: boolean, currentDirectory: string, getFileSystemEntries: (path: string) => { files: string[], directories: string[] }): string[];
export declare function getTypesAndSymbols(program: Program, fileName: string, checked: boolean, exclude: "types" | "symbols" | undefined): TypesAndSymbols[];
export declare function getOutputExtension(sourceFile: SourceFile, options: CompilerOptions): string;
+29
View File
@@ -0,0 +1,29 @@
import * as vpath from "./vpath";
import * as io from "./io";
import { toUtf8 } from "./utils";
import { assert } from "chai";
export interface BaselineOptions {
base?: string;
relative?: string;
local?: string;
reference?: string;
}
export function baseline(file: string, actual: string | undefined, opts: BaselineOptions = {}) {
const local = vpath.combine(opts.base || "tests/baselines", opts.relative || "", opts.local || "local", file);
const reference = vpath.combine(opts.base || "tests/baselines", opts.relative || "", opts.reference || "reference", file);
const expected = io.readFile(reference);
io.createDirectory(vpath.dirname(local));
io.deleteFile(local);
if (actual !== undefined) actual = toUtf8(actual);
if (expected !== actual) {
if (actual === undefined) {
io.writeFile(local + ".delete", "");
}
else {
io.writeFile(local, actual);
}
assert.fail(actual, expected, `The baseline file ${file} has changed.`);
}
}
+142
View File
@@ -0,0 +1,142 @@
import { compareValues, binarySearch, insertAt, removeAt } from "./utils";
export class KeyedCollection<K, V> {
private _comparer: (a: K, b: K) => number;
private _keys: K[] = [];
private _values: V[] = [];
private _order: number[] = [];
private _version = 0;
constructor(comparer: (a: K, b: K) => number = compareValues) {
this._comparer = comparer;
}
public get size() {
return this._keys.length;
}
public has(key: K) {
return binarySearch(this._keys, key, this._comparer) >= 0;
}
public get(key: K) {
const index = binarySearch(this._keys, key, this._comparer);
return index >= 0 ? this._values[index] : undefined;
}
public set(key: K, value: V) {
const index = binarySearch(this._keys, key, this._comparer);
if (index >= 0) {
this._values[index] = value;
}
else {
insertAt(this._keys, ~index, key);
insertAt(this._values, ~index, value);
insertAt(this._order, ~index, this._version);
this._version++;
}
return this;
}
public delete(key: K) {
const index = binarySearch(this._keys, key, this._comparer);
if (index >= 0) {
removeAt(this._keys, index);
removeAt(this._values, index);
removeAt(this._order, index);
this._version++;
return true;
}
return false;
}
public clear() {
this._keys.length = 0;
this._values.length = 0;
this._order.length = 0;
this._version = 0;
}
public forEach(callback: (value: V, key: K, collection: this) => void) {
let order = this.getInsertionOrder();
let version = this._version;
for (let i = 0; i < order.length; i++) {
callback(this._values[order[i]], this._keys[order[i]], this);
if (version !== this._version) {
order = this.getInsertionOrder();
version = this._version;
}
}
}
private getInsertionOrder() {
return this._order
.map((_, i) => i)
.sort((x, y) => compareValues(this._order[x], this._order[y]));
}
}
const undefinedSentinel = {};
export class Metadata {
private _parent: Metadata | undefined;
private _map: { [key: string]: any };
private _version = 0;
private _size: number | undefined;
private _parentVersion: number | undefined;
constructor(parent?: Metadata) {
this._parent = parent;
this._map = Object.create(parent ? parent._map : null); // tslint:disable-line:no-null-keyword
}
public get size(): number {
if (this._size === undefined || (this._parent && this._parent._version !== this._parentVersion)) {
let size = 0;
for (const _ in this._map) size++;
this._size = size;
if (this._parent) {
this._parentVersion = this._parent._version;
}
}
return this._size;
}
public has(key: string): boolean {
return this._map[key] !== undefined;
}
public get(key: string): any {
const value = this._map[key];
return value === undefinedSentinel ? undefined : value;
}
public set(key: string, value: any): this {
this._map[key] = value === undefined ? undefinedSentinel : value;
this._version++;
this._size = undefined;
return this;
}
public delete(key: string): boolean {
if (this._map[key] !== undefined) {
delete this._map[key];
this._version++;
this._size = undefined;
return true;
}
return false;
}
public clear(): void {
this._map = Object.create(this._parent ? this._parent._map : null); // tslint:disable-line:no-null-keyword
this._version++;
this._size = undefined;
}
public forEach(callback: (value: any, key: string, map: this) => void) {
for (const key in this._map) {
callback(this._map[key], key, this);
}
}
}
+337
View File
@@ -0,0 +1,337 @@
import * as ts from "./api";
import * as vpath from "./vpath";
import { VirtualFileSystem } from "./vfs";
import { TextDocument, isJavaScriptDocument, isDeclarationDocument, isSourceMapDocument } from "./documents";
import { isDeclarationFile, compareStrings, isDefaultLibraryFile, isJsonFile, removeComments, stripBOM } from "./utils";
import { SourceMap } from "./sourceMaps";
import { KeyedCollection } from "./collections";
export class CompilerHost {
private _setParentNodes: boolean;
private _sourceFiles = new Map<string, ts.SourceFile>();
private _newLine: string;
// public readonly ts: ts.TypeScript;
public readonly vfs: VirtualFileSystem;
public readonly defaultLibLocation: string;
public readonly outputs: TextDocument[] = [];
public readonly traces: string[] = [];
constructor(vfs: VirtualFileSystem, defaultLibLocation: string, newLine: "crlf" | "lf", setParentNodes = false) {
this.vfs = vfs;
this.defaultLibLocation = defaultLibLocation;
this._newLine = newLine === "crlf" ? "\r\n" : "\n";
this._setParentNodes = setParentNodes;
}
public getCurrentDirectory(): string {
return this.vfs.currentDirectory;
}
public useCaseSensitiveFileNames(): boolean {
return this.vfs.useCaseSensitiveFileNames;
}
public getNewLine(): string {
return this._newLine;
}
public getCanonicalFileName(fileName: string): string {
return this.vfs.useCaseSensitiveFileNames ? fileName : fileName.toLowerCase();
}
public fileExists(fileName: string): boolean {
return this.vfs.fileExists(fileName);
}
public directoryExists(directoryName: string): boolean {
return this.vfs.directoryExists(directoryName);
}
public getDirectories(path: string): string[] {
const entry = this.vfs.getDirectory(path);
return entry ? entry.getDirectories().map(dir => dir.name) : [];
}
public readFile(path: string): string | undefined {
const entry = this.vfs.getFile(path);
let content = entry && entry.getContent();
if (content) {
content = stripBOM(content);
return isJsonFile(path) ? removeComments(content) : content;
}
return undefined;
}
public writeFile(fileName: string, content: string, writeByteOrderMark: boolean) {
// NOTE(rbuckton): Old harness emits "\u00EF\u00BB\u00BF" for BOM, but compiler emits "\uFEFF". Compiler is wrong, as "\uFEFF" is a UTF16 BOM.
if (writeByteOrderMark) content = "\u00EF\u00BB\u00BF" + content;
const entry = this.vfs.addFile(fileName, content, { overwrite: true });
if (entry) {
const document = new TextDocument(entry.path, content);
entry.metadata.set("document", document);
const index = this.outputs.findIndex(doc => this.vfs.sameName(document.file, doc.file));
if (index < 0) {
this.outputs.push(document);
}
else {
this.outputs[index] = document;
}
}
}
public trace(s: string): void {
this.traces.push(s);
}
public realpath(path: string): string {
const entry = this.vfs.getEntry(path, { followSymlinks: true });
return entry && entry.path || path;
}
public getDefaultLibLocation(): string {
return vpath.resolve(this.vfs.currentDirectory, this.defaultLibLocation);
}
public getDefaultLibFileName(options: ts.CompilerOptions): string {
// FIXME(rbuckton): The old harness overrides the behavior for getting the default lib file
// name instead of using the correct one. Instead, we should be using `ts.getDefaultLibFileName`:
// return vpath.resolve(this.getDefaultLibLocation(), ts.getDefaultLibFileName(options));
return vpath.resolve(this.getDefaultLibLocation(), this.getDefaultLibFileNameWorker(options));
}
private getDefaultLibFileNameWorker(options: ts.CompilerOptions) {
switch (options.target) {
case ts.ScriptTarget.ESNext:
case ts.ScriptTarget.ES2017:
return "lib.es2017.d.ts";
case ts.ScriptTarget.ES2016:
return "lib.es2016.d.ts";
case ts.ScriptTarget.ES2015:
return "lib.es2015.d.ts";
default:
return "lib.d.ts";
}
}
public getSourceFile(fileName: string, languageVersion: number): ts.SourceFile | undefined {
fileName = this.getCanonicalFileName(vpath.resolve(this.vfs.currentDirectory, fileName));
const existing = this._sourceFiles.get(fileName);
if (existing) return existing;
const file = this.vfs.getFile(fileName);
// FIXME(rbuckton): The old harness reads _lib.es5.d.ts_ in place of _lib.d.ts_. We really
// shouldn't be doing that.
const patchedFileName = this.vfs.sameName(fileName, "/.ts/lib.d.ts") ? "/.ts/lib.es5.d.ts" : fileName;
const patchedFile = patchedFileName !== fileName ? this.vfs.getFile(patchedFileName) : file;
if (file && patchedFile) {
let content = patchedFile.getContent();
if (content !== undefined) {
content = stripBOM(content);
// We cache and reuse source files for files we know shouldn't change.
const shouldCache = isDefaultLibraryFile(fileName) ||
vpath.beneath("/.ts", file.path, !this.vfs.useCaseSensitiveFileNames) ||
vpath.beneath("/.lib", file.path, !this.vfs.useCaseSensitiveFileNames);
const cacheKey = shouldCache && `SourceFile[languageVersion=${languageVersion},setParentNodes=${this._setParentNodes}]`;
if (cacheKey) {
const sourceFileFromMetadata = file.metadata.get(cacheKey) as ts.SourceFile | undefined;
if (sourceFileFromMetadata) {
this._sourceFiles.set(fileName, sourceFileFromMetadata);
return sourceFileFromMetadata;
}
}
const parsed = ts.createSourceFile(fileName, content, languageVersion, this._setParentNodes);
this._sourceFiles.set(fileName, parsed);
if (cacheKey) {
// store the cached source file on the unshadowed file.
let rootFile = file;
while (rootFile.shadowRoot) rootFile = rootFile.shadowRoot;
rootFile.metadata.set(cacheKey, parsed);
}
return parsed;
}
}
}
}
export interface CompilationOutput {
readonly input: TextDocument;
readonly js: TextDocument | undefined;
readonly dts: TextDocument | undefined;
readonly map: TextDocument | undefined;
}
export class CompilationResult {
public readonly host: CompilerHost;
public readonly program: ts.Program;
public readonly result: ts.EmitResult;
public readonly diagnostics: ts.Diagnostic[];
public readonly js: KeyedCollection<string, TextDocument>;
public readonly dts: KeyedCollection<string, TextDocument>;
public readonly maps: KeyedCollection<string, TextDocument>;
private _inputsAndOutputs: KeyedCollection<string, CompilationOutput>;
constructor(host: CompilerHost, program: ts.Program, result: ts.EmitResult, diagnostics: ts.Diagnostic[]) {
this.host = host;
this.program = program;
this.result = result;
this.diagnostics = diagnostics;
// collect outputs
const pathComparer = this.vfs.useCaseSensitiveFileNames ? compareStrings.caseSensitive : compareStrings.caseInsensitive;
this.js = new KeyedCollection<string, TextDocument>(pathComparer);
this.dts = new KeyedCollection<string, TextDocument>(pathComparer);
this.maps = new KeyedCollection<string, TextDocument>(pathComparer);
for (const document of this.host.outputs) {
if (isJavaScriptDocument(document)) {
this.js.set(document.file, document);
}
else if (isDeclarationDocument(document)) {
this.dts.set(document.file, document);
}
else if (isSourceMapDocument(document)) {
this.maps.set(document.file, document);
}
}
// correlate inputs and outputs
this._inputsAndOutputs = new KeyedCollection<string, CompilationOutput>(pathComparer);
for (const sourceFile of program.getSourceFiles()) {
if (sourceFile && !isDeclarationFile(sourceFile.fileName)) {
const file = host.vfs.getFile(sourceFile.fileName);
let input = file && file.metadata.get("document");
if (!input) {
input = new TextDocument(file ? file.path : sourceFile.fileName, file && file.getContent() || sourceFile.text);
if (file) file.metadata.set("document", input);
}
const outputs = {
input,
js: this.js.get(this.getOutputPath(sourceFile.fileName, ts.getOutputExtension(sourceFile, this.options))),
dts: this.dts.get(this.getOutputPath(sourceFile.fileName, ".d.ts", this.options.declarationDir)),
map: this.maps.get(this.getOutputPath(sourceFile.fileName, ts.getOutputExtension(sourceFile, this.options) + ".map"))
};
this._inputsAndOutputs.set(sourceFile.fileName, outputs);
if (outputs.js) this._inputsAndOutputs.set(outputs.js.file, outputs);
if (outputs.dts) this._inputsAndOutputs.set(outputs.dts.file, outputs);
if (outputs.map) this._inputsAndOutputs.set(outputs.map.file, outputs);
}
}
}
public get vfs() {
return this.host.vfs;
}
public get options() {
return this.program.getCompilerOptions();
}
public get outputs() {
return this.host.outputs;
}
public get traces(): string[] {
return this.host.traces;
}
public get emitSkipped(): boolean {
return this.result.emitSkipped;
}
public get singleFile() {
return !!this.options.outFile || !!this.options.out;
}
public get commonSourceDirectory() {
const common = this.program.getCommonSourceDirectory();
return common && vpath.combine(this.vfs.currentDirectory, common);
}
public getInputsAndOutputs(path: string) {
return this._inputsAndOutputs.get(vpath.resolve(this.vfs.currentDirectory, path));
}
public getInput(path: string) {
const outputs = this.getInputsAndOutputs(path);
return outputs && outputs.input;
}
public getOutput(path: string, kind: "js" | "dts" | "map") {
const outputs = this.getInputsAndOutputs(path);
return outputs && outputs[kind];
}
public getSourceMap(path: string) {
if (this.options.noEmit || isDeclarationFile(path)) return undefined;
if (this.options.inlineSourceMap) {
const document = this.getOutput(path, "js");
return document && SourceMap.fromSource(document.text);
}
if (this.options.sourceMap) {
const document = this.getOutput(path, "map");
return document && new SourceMap(document.file, document.text);
}
}
public getOutputPath(path: string, ext: string, outDir: string | undefined = this.options.outDir) {
if (outDir) {
path = vpath.resolve(this.vfs.currentDirectory, path);
const common = this.program.getCommonSourceDirectory();
if (!common) return vpath.chext(path, ext);
path = vpath.relative(common, path, !this.vfs.useCaseSensitiveFileNames);
path = vpath.combine(vpath.resolve(this.vfs.currentDirectory, outDir), path);
return vpath.chext(path, ext);
}
const outFile = vpath.resolve(this.vfs.currentDirectory, this.options.outFile || this.options.out || path);
return vpath.chext(outFile, ext);
}
}
export class ParseConfigHost {
public readonly vfs: VirtualFileSystem;
constructor(vfs: VirtualFileSystem) {
this.vfs = vfs;
}
public get useCaseSensitiveFileNames() {
return this.vfs.useCaseSensitiveFileNames;
}
public readDirectory(path: string, extensions: string[], excludes: string[], includes: string[]): string[] {
return ts.matchFiles(path, extensions, excludes, includes, this.vfs.useCaseSensitiveFileNames, this.vfs.currentDirectory, path => this.vfs.getAccessibleFileSystemEntries(path));
}
public fileExists(path: string) {
return this.vfs.fileExists(path);
}
public readFile(path: string) {
const entry = this.vfs.getFile(path);
return entry && entry.getContent();
}
}
export function compileFiles(vfs: VirtualFileSystem, defaultLibLocation: string, rootFiles: string[], options: ts.CompilerOptions) {
// establish defaults (aligns with old harness)
if (options.target === undefined) options.target = ts.ScriptTarget.ES3;
if (options.newLine === undefined) options.newLine = ts.NewLineKind.CarriageReturnLineFeed;
if (options.skipDefaultLibCheck === undefined) options.skipDefaultLibCheck = true;
if (options.noErrorTruncation === undefined) options.noErrorTruncation = true;
const host = new CompilerHost(vfs, defaultLibLocation, options.newLine === ts.NewLineKind.CarriageReturnLineFeed ? "crlf" : "lf");
const program = ts.createProgram(rootFiles, options, host);
const emitResult = program.emit();
const errors = ts.getPreEmitDiagnostics(program);
return new CompilationResult(host, program, emitResult, errors);
}
+35
View File
@@ -0,0 +1,35 @@
import * as io from "./io";
import { ParsedArguments } from "./options";
import { TestRunTask } from "./runner";
export interface TestConfig {
light?: boolean;
taskConfigsFolder?: string;
workerCount?: number;
stackTraceLimit?: number | "full";
tasks?: TestRunTask[];
test?: string[];
runUnitTests?: boolean;
}
/**
* Get the current test configuration.
*/
export function getTestConfig(args: ParsedArguments): TestConfig {
const content = getTestConfigContent();
const config: TestConfig = content ? JSON.parse(content) : { };
if (config.light === undefined) config.light = false;
if (config.workerCount === undefined) config.workerCount = 0;
if (config.runUnitTests === undefined) config.runUnitTests = !args.discover;
return config;
/**
* Read the test configuration from either the command line, a
* custom _mytest.config_ file, or the default _test.config_ file.
*/
function getTestConfigContent(): string | undefined {
return args.config && io.readFile(args.config)
|| io.readFile("mytest.config")
|| io.readFile("test.config");
}
}
+39
View File
@@ -0,0 +1,39 @@
import { isTypeScriptFile, isJavaScriptFile, isDeclarationFile, isSourceMapFile, isJsonFile, computeLineStarts } from "./utils";
export class TextDocument {
public readonly meta: Map<string, string>;
public readonly file: string;
public readonly text: string;
private _lineStarts: number[] | undefined;
constructor(file: string, content: string, meta?: Map<string, string>) {
this.file = file;
this.text = content;
this.meta = meta || new Map<string, string>();
}
public get lineStarts(): number[] {
return this._lineStarts || (this._lineStarts = computeLineStarts(this.text));
}
}
export function isTypeScriptDocument(document: TextDocument) {
return isTypeScriptFile(document.file);
}
export function isJavaScriptDocument(document: TextDocument) {
return isJavaScriptFile(document.file);
}
export function isDeclarationDocument(document: TextDocument) {
return isDeclarationFile(document.file);
}
export function isSourceMapDocument(document: TextDocument) {
return isSourceMapFile(document.file);
}
export function isJsonDocument(document: TextDocument) {
return isJsonFile(document.file);
}
+110
View File
@@ -0,0 +1,110 @@
import * as ts from "../api";
import * as vpath from "../vpath";
import { TextDocument } from "../documents";
import { TextWriter } from "../textWriter";
import { isDefaultLibraryFile, repeatString, isBuiltFile, splitLines, compareValues, removeTestPathPrefixes, getLinesAndLineStarts } from "../utils";
import { assert } from "chai";
import { CompilationResult } from "../compiler";
function compareDiagnostics(a: ts.Diagnostic, b: ts.Diagnostic) {
if (a.file && b.file) {
return compareValues(!isDefaultLibraryFile(a.file.fileName), !isDefaultLibraryFile(b.file.fileName))
|| ts.compareDiagnostics(a, b);
}
return ts.compareDiagnostics(a, b);
}
export function formatDiagnostics(documents: TextDocument[], result: CompilationResult) {
const diagnostics = result.diagnostics;
diagnostics.sort(compareDiagnostics);
let numNonLibraryDiagnostics = 0;
let numLibraryDiagnostics = 0;
let numTest262HarnessDiagnostics = 0;
const writer = new TextWriter(removeTestPathPrefixes(ts.formatDiagnostics(diagnostics, {
getCanonicalFileName: path => path,
getCurrentDirectory: () => "",
getNewLine: () => "\r\n"
})));
writer.writeln();
// write global diagnostics first
for (const diagnostic of diagnostics) {
if (!diagnostic.file) {
writeDiagnostic(diagnostic);
}
else if (isDefaultLibraryFile(diagnostic.file.fileName) || isBuiltFile(diagnostic.file.fileName)) {
numLibraryDiagnostics++;
}
else if (diagnostic.file.fileName.includes("test262-harness")) {
numTest262HarnessDiagnostics++;
}
}
// write file diagnostics
for (const document of documents) {
const path = vpath.resolve(result.vfs.currentDirectory, document.file);
const fileDiagnostics = diagnostics.filter(diagnostic => {
const file = diagnostic.file;
return file !== undefined && result.vfs.sameName(file.fileName, path);
});
writer.writeln().write(`==== ${document.file} (${fileDiagnostics.length} errors) ====`);
let numMarkedDiagnostics = 0;
// For each line, emit the line followed by any error squiggles matching this line
const { lines, lineStarts } = getLinesAndLineStarts(document.text);
for (let lineIndex = 0; lineIndex < lines.length; lineIndex++) {
const line = lines[lineIndex];
const thisLineStart = lineStarts[lineIndex];
const nextLineStart = lineIndex === lines.length - 1 ? document.text.length : lineStarts[lineIndex + 1];
// Emit this line from the original file
writer.writeln().write(` ${line}`);
for (const diagnostic of fileDiagnostics) {
if (diagnostic.start === undefined || diagnostic.length === undefined) continue;
const end = diagnostic.start + diagnostic.length;
// Does any error start or continue on to this line? Emit squiggles
if (end >= thisLineStart && (diagnostic.start < nextLineStart || lineIndex === lines.length - 1)) {
// How many characters from the start of this line the error starts at (could be positive or negative)
const relativeOffset = diagnostic.start - thisLineStart;
// How many characters of the error are on this line (might be longer than this line in reality)
const length = diagnostic.length - Math.max(0, thisLineStart - diagnostic.start);
// Calculate the start of the squiggle
const squiggleStart = Math.max(0, relativeOffset);
const squiggleLength = Math.min(length, line.length - squiggleStart);
const prefix = line.slice(0, squiggleStart).replace(/\S/g, " ");
const squiggle = repeatString("~", squiggleLength);
writer.writeln().write(` ${prefix}${squiggle}`);
// If the error ended here, or we're at the end of the file, emit its message
if ((lineIndex === lines.length - 1) || nextLineStart > end) {
writeDiagnostic(diagnostic);
numMarkedDiagnostics++;
}
}
}
}
assert.lengthOf(fileDiagnostics, numMarkedDiagnostics, `Incorrect number of marked errors in ${document.file}`);
}
assert.lengthOf(diagnostics, numNonLibraryDiagnostics + numLibraryDiagnostics + numTest262HarnessDiagnostics, "total number of errors");
return writer.toString();
function writeDiagnostic(diagnostic: ts.Diagnostic) {
writer.writeln().write(formatDiagnostic(/*ts,*/ diagnostic));
if (!diagnostic.file || !isDefaultLibraryFile(diagnostic.file.fileName)) {
numNonLibraryDiagnostics++;
}
}
}
function formatDiagnostic(diagnostic: ts.Diagnostic) {
const category = ts.DiagnosticCategory[diagnostic.category];
return splitLines(ts.flattenDiagnosticMessageText(diagnostic.messageText, "\r\n"), /*removeEmptyElements*/ true)
.map(line => `!!! ${category ? category.toLowerCase() : ""} TS${diagnostic.code}: ${line}`)
.join("\r\n")
.replace(/\/\.test\//g, "");
}
+6
View File
@@ -0,0 +1,6 @@
export { formatJavaScript } from "./javaScript";
export { formatDiagnostics } from "./diagnostics";
export { formatSourceMapData, formatSourceMaps } from "./sourceMaps";
export { formatModuleResolution } from "./moduleResolution";
export { formatTypes } from "./types";
export { formatSymbols } from "./symbols";
+57
View File
@@ -0,0 +1,57 @@
import * as vpath from "../vpath";
import { TextDocument } from "../documents";
import { TextWriter } from "../textWriter";
import { CompilationResult } from "../compiler";
import { formatDiagnostics } from "./diagnostics";
export function formatJavaScript(header: string, fullEmitPaths: boolean, documents: TextDocument[], result: CompilationResult, declarationDocuments: TextDocument[] | undefined, declarationResult: CompilationResult | undefined) {
const writer = new TextWriter();
// add header if needed
if (documents.length > 1) writer.writeln(`//// [${header}] ////`);
// add each input document
for (const document of documents) {
if (writer.size > 0) writer.writeln();
writer.writeln(`//// [${vpath.basename(document.file)}]`);
writer.write(document.text);
}
// add space between ts and js/dts emit
if (result.js.size > 0 || result.dts.size > 0 || (declarationResult && declarationResult.diagnostics.length > 0)) {
writer.writeln();
writer.writeln();
}
// add each script output
result.js.forEach(document => {
const file = fullEmitPaths ? document.file : vpath.basename(document.file);
writer.writeln(`//// [${file}]`);
writer.write(document.text);
});
// Add space between js and dts emit
if (result.js.size > 0 && result.dts.size > 0) {
writer.writeln();
writer.writeln();
}
// add each declaration output
result.dts.forEach(document => {
const file = fullEmitPaths ? document.file : vpath.basename(document.file);
writer.writeln(`//// [${file}]`);
writer.write(document.text);
});
// add declaration diagnostics
if (declarationDocuments && declarationResult && declarationResult.diagnostics.length > 0) {
writer.writeln();
writer.writeln();
writer.writeln(`//// [DtsFileErrors]`);
writer.writeln();
writer.writeln();
writer.write(formatDiagnostics(declarationDocuments, declarationResult));
}
return writer.toString();
}
@@ -0,0 +1,7 @@
import { CompilationResult } from "../compiler";
import { removeTestPathPrefixes } from "../utils";
export function formatModuleResolution(result: CompilationResult) {
const lines = result.traces.map(removeTestPathPrefixes);
return JSON.stringify(lines, /*replacer*/ undefined, " ");
}
+123
View File
@@ -0,0 +1,123 @@
import * as vpath from "../vpath";
import { assert } from "chai";
import { TextWriter } from "../textWriter";
import { CompilationResult } from "../compiler";
import { SourceMap, Mapping } from "../sourceMaps";
import { TextDocument } from "../documents";
import { computeLineStarts, padRight, repeatString } from "../utils";
export function formatSourceMapData(result: CompilationResult) {
const writer = new TextWriter();
result.js.forEach(emittedDocument => {
const sourceMap = result.getSourceMap(emittedDocument.file);
if (sourceMap) {
writer.writeln("===================================================================");
writer.writeln("JsFile: " + vpath.basename(emittedDocument.file));
const mapUrl = !result.options.inlineSourceMap ? SourceMap.getUrl(emittedDocument.text) : undefined;
writer.writeln("mapUrl: " + mapUrl);
writer.writeln("sourceRoot: " + sourceMap.sourceMap.sourceRoot);
writer.writeln("sources: " + sourceMap.sourceMap.sources);
if (sourceMap.sourceMap.sourcesContent) {
writer.writeln("sourcesContent: " + JSON.stringify(sourceMap.sourceMap.sourcesContent));
}
writer.writeln("===================================================================");
const sources = sourceMap.sources.map(source => {
const file = vpath.combine(result.vfs.currentDirectory, result.commonSourceDirectory, source.file);
const document = result.getInput(file)!;
assert.isDefined(document);
const lineStarts = computeLineStarts(document.text);
return { document, lineStarts };
});
const emittedLineStarts = emittedDocument.lineStarts;
for (let i = 0; i < emittedLineStarts.length; i++) {
const lineStart = emittedLineStarts[i];
const lineEnd = i < emittedLineStarts.length - 1 ? emittedLineStarts[i + 1] : emittedDocument.text.length;
const line = emittedDocument.text.slice(lineStart, lineEnd);
writer.write(`>>>${line}`);
const mappings = sourceMap.getMappingsForEmittedLine(i);
if (mappings) {
let column = 0;
for (let i = 0; i < mappings.length; i++) {
const mapping = mappings[i];
writer.write(`${padRight("" + (i + 1), 2)}>`);
writer.write(repeatString(" ", column));
writer.write(repeatString("^", mapping.emittedColumn - column));
writer.writeln();
column = mapping.emittedColumn;
}
column = 0;
let sourceColumn = 0;
for (let i = 0; i < mappings.length; i++) {
const mapping = mappings[i];
const source = sources[mapping.sourceIndex];
const sourceLineStart = source.lineStarts[mapping.sourceLine];
const sourceLineEnd = mapping.sourceLine < source.lineStarts.length - 1 ? source.lineStarts[mapping.sourceLine + 1] : source.document.text.length;
const sourceLine = source.document.text.slice(sourceLineStart, sourceLineEnd);
writer.write(`${padRight("" + (i + 1), 2)}>`);
writer.write(repeatString(" ", column));
writer.write(sourceLine.slice(sourceColumn, mapping.sourceColumn));
writer.writeln();
sourceColumn = mapping.sourceColumn;
column = mapping.emittedColumn;
}
column = 0;
for (let i = 0; i < mappings.length; i++) {
const mapping = mappings[i];
writer.write(`${padRight("" + (i + 1), 2)}>`);
writer.write(`Emitted(${mapping.emittedLine + 1}, ${mapping.emittedColumn + 1}) Source(${mapping.sourceLine + 1}, ${mapping.sourceColumn + 1}) + SourceIndex(${mapping.sourceIndex})`);
if (mapping.name) {
writer.write(` name (${mapping.name})`);
}
else if (mapping.nameIndex !== undefined) {
writer.write(` nameIndex (${mapping.nameIndex})`);
}
writer.writeln();
column = mapping.emittedColumn;
}
}
// if (mappings && mappings.length) {
// for (const mapping of mappings) {
// if (mapping.sourceIndex !== currentSourceIndex) {
// const file = vpath.combine(result.vfs.currentDirectory, result.commonSourceDirectory, mapping.source.file);
// const source = result.getInput(file)!;
// assert.isDefined(source);
// writer.writeln("-------------------------------------------------------------------");
// writer.writeln("emittedFile:" + document.file);
// writer.writeln("sourceFile:" + mapping.source.file);
// writer.writeln("-------------------------------------------------------------------");
// currentSource = source;
// currentSourceIndex = mapping.sourceIndex;
// hasEmittedMappings = true;
// }
// }
// }
// if (hasEmittedMappings) {
// while (lastEmittedLine < i) {
// const lineStart = emittedLineStarts[lastEmittedLine];
// const lineEnd = lastEmittedLine < emittedLineStarts.length - 1 ? emittedLineStarts[lastEmittedLine + 1] : document.text.length;
// const line = document.text.slice(lineStart, lineEnd);
// writer.write(`>>>${line}`);
// lastEmittedLine++;
// }
// }
}
}
});
return writer.toString();
}
export function formatSourceMaps(fullEmitPaths: boolean, result: CompilationResult) {
const writer = new TextWriter();
result.maps.forEach(document => {
const file = fullEmitPaths ? document.file : vpath.basename(document.file);
writer.writeln(`//// [${file}]`);
writer.write(document.text);
});
return writer.toString();
}
+71
View File
@@ -0,0 +1,71 @@
import * as ts from "../api";
import * as vpath from "../vpath";
import { TextDocument, isTypeScriptDocument, isJavaScriptDocument } from "../documents";
import { TextWriter } from "../textWriter";
import { splitLines, removeTestPathPrefixes } from "../utils";
const leadingOrTrailingBraceRegExp = /^\s*[{}]\s*$/;
export function formatSymbols(documents: TextDocument[], typesAndSymbols: Map<string, ts.TypesAndSymbols[]>) {
const writer = new TextWriter();
for (const document of documents) {
if (!isTypeScriptDocument(document) && !isJavaScriptDocument(document)) continue;
writer.writeln(`=== ${document.file} ===`);
const results = typesAndSymbols.get(document.file);
let lineMap: Map<number, string[]> | undefined;
if (results) {
for (const result of results) {
if (!result.symbol) continue;
if (!lineMap) lineMap = new Map<number, string[]>();
let lineInfo = lineMap.get(result.line);
if (!lineInfo) lineMap.set(result.line, lineInfo = []);
lineInfo.push(result.text.replace(/\r\n?|\n/g, "") + " : " + formatSymbol(result.symbol, result.declarations));
}
}
// TODO(rbuckton): We should consider switching to the following commented code below
// to address the FIXME immediately below.
// if (!lineMap) {
// writer.writeln("No symbol information for this code.");
// continue;
// }
const lines = splitLines(document.text);
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
writer.writeln(line);
if (!lineMap) {
// FIXME(rbuckton): The old harness writes this over and over. We may want to prefer the
// commented version above instead.
writer.write("No type information for this code.");
continue;
}
const lineInfo = lineMap.get(i);
if (lineInfo) {
for (const symbol of lineInfo) {
writer.writeln(`>${removeTestPathPrefixes(symbol)}`);
}
const nextLine = i < lines.length - 1 ? lines[i + 1] : undefined;
if (nextLine === undefined || (!leadingOrTrailingBraceRegExp.test(nextLine) && nextLine.trim())) {
writer.writeln();
}
}
}
}
return writer.toString();
}
function formatSymbol(symbol: string, declarations: { fileName: string, line: number, character: number }[] | undefined) {
const writer = new TextWriter();
writer.write("Symbol(");
writer.write(symbol);
if (declarations) {
for (const decl of declarations) {
const basename = vpath.basename(decl.fileName);
// FIXME(rbuckton): The following incorrectly catches `tslib.d.ts`. We should consider
// switching to the commented line below.
const isLibFile = /lib(.*)\.d\.ts/i.test(basename);
// const isLibFile = isDefaultLibraryFile(basename);
writer.write(`, Decl(${basename}, ${isLibFile ? "--" : decl.line}, ${isLibFile ? "--" : decl.character})`);
}
}
writer.write(")");
return writer.toString();
}
+53
View File
@@ -0,0 +1,53 @@
import * as ts from "../api";
import { TextDocument, isTypeScriptDocument, isJavaScriptDocument } from "../documents";
import { TextWriter } from "../textWriter";
import { splitLines, removeTestPathPrefixes } from "../utils";
import { assert } from "chai";
const leadingOrTrailingBraceRegExp = /^\s*[{}]\s*$/;
export function formatTypes(documents: TextDocument[], typesAndSymbols: Map<string, ts.TypesAndSymbols[]>) {
const writer = new TextWriter();
for (const document of documents) {
if (!isTypeScriptDocument(document) && !isJavaScriptDocument(document)) continue;
writer.writeln(`=== ${document.file} ===`);
const results = typesAndSymbols.get(document.file);
let lineMap: Map<number, string[]> | undefined;
if (results) {
for (const result of results) {
assert.isDefined(result.type, "type doesn't exist");
if (!lineMap) lineMap = new Map<number, string[]>();
let lineInfo = lineMap.get(result.line);
if (!lineInfo) lineMap.set(result.line, lineInfo = []);
lineInfo.push(result.text.replace(/\r\n?|\n/g, "") + " : " + result.type);
}
}
// TODO(rbuckton): We should consider switching to the following commented code below
// to address the FIXME immediately below.
// if (!lineMap) {
// writer.writeln("No type information for this code.");
// continue;
// }
const lines = splitLines(document.text);
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
writer.writeln(line);
if (!lineMap) {
// FIXME(rbuckton): The old harness writes this over and over. We may want to prefer the
// commented version above instead.
writer.write("No type information for this code.");
continue;
}
const lineInfo = lineMap.get(i);
if (lineInfo) {
for (const type of lineInfo) {
writer.writeln(`>${removeTestPathPrefixes(type)}`);
}
const nextLine = i < lines.length - 1 ? lines[i + 1] : undefined;
if (nextLine === undefined || (!leadingOrTrailingBraceRegExp.test(nextLine) && nextLine.trim())) {
writer.writeln();
}
}
}
}
return writer.toString();
}
+391
View File
@@ -0,0 +1,391 @@
import * as Utils from "./utils";
import * as VirtualPath from "./vpath";
import { VirtualFileSystem } from "./vfs";
interface IO {
newLine(): string;
getCurrentDirectory(): string;
useCaseSensitiveFileNames(): boolean;
resolvePath(path: string): string | undefined;
readFile(path: string): string | undefined;
writeFile(path: string, contents: string): void;
directoryName(path: string): string | undefined;
getDirectories(path: string): string[];
createDirectory(path: string): void;
fileExists(fileName: string): boolean;
directoryExists(path: string): boolean;
deleteFile(fileName: string): void;
listFiles(path: string, filter: RegExp, options?: { recursive?: boolean }): string[];
log(text: string): void;
getMemoryUsage?(): number;
args(): string[];
getExecutingFilePath(): string;
exit(exitCode?: number): void;
readDirectory(path: string, extension?: string[], exclude?: string[], include?: string[]): string[];
tryEnableSourceMapsForHost?(): void;
getEnvironmentVariable?(name: string): string;
}
let matchFiles: ((path: string, extensions: string[] | undefined, excludes: string[] | undefined, includes: string[] | undefined, useCaseSensitiveFileNames: boolean, currentDirectory: string, getFileSystemEntries: (path: string) => { files: string[], directories: string[] }) => string[]) | undefined;
export function setFileMatcher(value: (path: string, extensions: string[], excludes: string[], includes: string[], useCaseSensitiveFileNames: boolean, currentDirectory: string, getFileSystemEntries: (path: string) => { files: string[], directories: string[] }) => string[]) {
matchFiles = value;
}
function createNodeIO(): IO {
const fs = require("fs");
const path = require("path");
const os = require("os");
const platform = os.platform();
const useCaseSensitiveFileNames = isFileSystemCaseSensitive();
const args = process.argv.slice(2);
const executingFilePath = path.join(__dirname, "run.js");
return {
newLine() {
return "\r\n";
},
useCaseSensitiveFileNames() {
return useCaseSensitiveFileNames;
},
getCurrentDirectory() {
return process.cwd();
},
getExecutingFilePath() {
return executingFilePath;
},
args() {
return args;
},
log(text: string) {
console.log(text);
},
exit(exitCode?: number) {
process.exit(exitCode);
},
resolvePath(name: string) {
return path.resolve(name);
},
readFile,
writeFile,
directoryName,
getDirectories,
createDirectory(name: string) {
tryExec(() => fs.mkdirSync(name));
},
fileExists,
directoryExists,
deleteFile(name: string) {
tryExec(() => fs.unlinkSync(name));
},
listFiles(dirname: string, spec?: RegExp, options?: { recursive?: boolean }): string[] {
return filesInFolder(dirname, options && options.recursive || false);
function filesInFolder(folder: string, recursive: boolean): string[] {
let paths: string[] = [];
const files = fs.readdirSync(folder);
for (let i = 0; i < files.length; i++) {
const pathToFile = path.join(folder, files[i]);
const stat = fs.statSync(pathToFile);
if (recursive && stat.isDirectory()) {
paths = paths.concat(filesInFolder(pathToFile, /*recursive*/ true));
}
else if (stat.isFile() && (!spec || files[i].match(spec))) {
paths.push(pathToFile);
}
}
return paths;
}
},
getMemoryUsage() {
if (global.gc) {
global.gc();
}
return process.memoryUsage().heapUsed;
},
readDirectory,
tryEnableSourceMapsForHost() {
tryExec(() => require("source-map-support").install());
},
getEnvironmentVariable(name: string) {
return process.env[name] || "";
}
};
function isFileSystemCaseSensitive() {
if (platform === "win32" || <string>platform === "win64") {
return false;
}
return !fileExists(__filename.toUpperCase())
|| !fileExists(__filename.toLowerCase());
}
function tryExec<T>(func: () => T): T | undefined {
try {
return func();
}
catch (e) {
return undefined;
}
}
function fileExists(name: string): boolean {
return tryExec(() => fs.statSync(name).isFile()) || false;
}
function directoryExists(name: string): boolean {
return tryExec(() => fs.statSync(name).isDirectory()) || false;
}
function readFile(name: string) {
return tryExec<string>(() => {
const buffer = fs.readFileSync(name);
let len = buffer.length;
if (len >= 2 && buffer[0] === 0xFE && buffer[1] === 0xFF) {
// Big endian UTF-16 byte order mark detected. Since big endian is not supported by node.js,
// flip all byte pairs and treat as little endian.
len &= ~1; // Round down to a multiple of 2
for (let i = 0; i < len; i += 2) {
const temp = buffer[i];
buffer[i] = buffer[i + 1];
buffer[i + 1] = temp;
}
return buffer.toString("utf16le", 2);
}
if (len >= 2 && buffer[0] === 0xFF && buffer[1] === 0xFE) {
// Little endian UTF-16 byte order mark detected
return buffer.toString("utf16le", 2);
}
if (len >= 3 && buffer[0] === 0xEF && buffer[1] === 0xBB && buffer[2] === 0xBF) {
// UTF-8 byte order mark detected
return buffer.toString("utf8", 3);
}
// Default is UTF-8 with no byte order mark
return buffer.toString("utf8");
});
}
function writeFile(name: string, contents: string) {
fs.writeFileSync(name, contents, "utf8");
}
function directoryName(name: string) {
const dir = path.dirname(name);
// Node will just continue to repeat the root path, rather than return null
return dir === name ? undefined : dir;
}
function getDirectories(name: string): string[] {
return fs.readdirSync(name).filter((dir: string) => directoryExists(path.join(name, dir)));
}
function readDirectory(path: string, extensions?: string[], excludes?: string[], includes?: string[]): string[] {
if (!matchFiles) {
throw new Error("File matcher not defined.");
}
return matchFiles(path, extensions, excludes, includes, useCaseSensitiveFileNames, process.cwd(), getAccessibleFileSystemEntries);
}
function getAccessibleFileSystemEntries(dirname: string): { files: string[], directories: string[] } {
try {
const entries = fs.readdirSync(dirname || ".").sort();
const files: string[] = [];
const directories: string[] = [];
for (const entry of entries) {
// This is necessary because on some file system node fails to exclude
// "." and "..". See https://github.com/nodejs/node/issues/4002
if (entry === "." || entry === "..") {
continue;
}
const name = path.join(dirname, entry);
let stat: any;
try {
stat = fs.statSync(name);
}
catch (e) {
continue;
}
if (stat.isFile()) {
files.push(entry);
}
else if (stat.isDirectory()) {
directories.push(entry);
}
}
return { files, directories };
}
catch (e) {
return { files: [], directories: [] };
}
}
}
declare var XMLHttpRequest: {
new (): XMLHttpRequest;
};
interface XMLHttpRequest {
readonly readyState: number;
readonly responseText: string;
readonly status: number;
open(method: string, url: string, async?: boolean, user?: string, password?: string): void;
send(data?: string): void;
setRequestHeader(header: string, value: string): void;
}
function createNetworkIO(): IO {
const serverRoot = "http://localhost:8888/";
const args: string[] = [];
const vfs = new VirtualFileSystem("", false);
interface Response {
status: number;
responseText?: string;
}
return {
newLine() {
return "\r\n";
},
useCaseSensitiveFileNames() {
return vfs.useCaseSensitiveFileNames;
},
getCurrentDirectory() {
return vfs.currentDirectory;
},
getExecutingFilePath() {
return "";
},
args() {
return args;
},
log(s: string) {
console.log(s);
},
exit(_?: number) {
},
resolvePath(name: string) {
const response = send("POST", `${serverRoot}${name}?action=resolve`);
return response.status === 200 ? response.responseText : undefined;
},
readFile(name: string) {
const response = send("GET", `${serverRoot}${name}`);
return response.status === 200 ? response.responseText : undefined;
},
writeFile(name: string, contents: string) {
send("PUT", `${serverRoot}${name}`, contents);
},
directoryName(name: string) {
return VirtualPath.dirname(name);
},
getDirectories(_: string): string[] {
return [];
},
createDirectory(_: string) { },
fileExists(name: string) {
return send("HEAD", `${serverRoot}${name}`).status === 200;
},
directoryExists(_: string) {
return false;
},
deleteFile(name: string) {
send("POST", `${serverRoot}${name}?action=DELETE`);
},
listFiles,
readDirectory
};
function send(method: "GET" | "HEAD" | "PUT" | "POST" | "DELETE", url: string, content?: string): Response {
try {
const xhr = new XMLHttpRequest();
xhr.open(method, url, /*async*/ false);
xhr.setRequestHeader("Access-Control-Allow-Origin", "*");
xhr.send(content);
while (xhr.readyState !== 4);
return xhr;
}
catch (e) {
console.log(`XHR Error: ${e}`);
return { status: 500 };
}
}
// function directoryNameImpl(path: string) {
// let dirPath = path;
// // root of the server
// if (dirPath.match(/localhost:\d+$/) || dirPath.match(/localhost:\d+\/$/)) {
// dirPath = undefined;
// // path + fileName
// }
// else if (dirPath.indexOf(".") === -1) {
// dirPath = dirPath.substring(0, dirPath.lastIndexOf("/"));
// // path
// }
// else {
// // strip any trailing slash
// if (dirPath.match(/.*\/$/)) {
// dirPath = dirPath.substring(0, dirPath.length - 2);
// }
// dirPath = dirPath.substring(0, dirPath.lastIndexOf("/"));
// }
// return dirPath;
// }
function listFiles(_path: string, _spec?: RegExp): string[] {
throw new Error("Not implemented");
// let vdir = vfs.getDirectory(path);
// if (vdir === undefined) {
// const response = send("GET", `${serverRoot}${path}`);
// if (response.status === 200 && response.responseText) {
// const results = response.responseText.split(",");
// return spec ? results.filter(file => spec.test(file)) : results;
// }
// }
// if (vdir) {
// const results = vdir.getFiles(true).map(file => file.fullName);
// return spec ? results.filter(file => spec.test(file)) : results;
// }
// return [];
}
function readDirectory(_path: string, _extensions?: string[], _excludes?: string[], _includes?: string[]): string[] {
throw new Error("Not implemented");
// return matchFiles(path, extensions, excludes, includes, vfs.useCaseSensitiveFileNames, process.cwd(), getAccessibleFileSystemEntries);
}
}
function createIO() {
const environment = Utils.getExecutionEnvironment();
switch (environment) {
case Utils.ExecutionEnvironment.Node: return createNodeIO();
case Utils.ExecutionEnvironment.Browser: return createNetworkIO();
default: throw new Error(`Unknown value '${environment}' for ExecutionEnvironment.`);
}
}
export const {
newLine,
useCaseSensitiveFileNames,
getCurrentDirectory,
getExecutingFilePath,
args,
log,
exit,
resolvePath,
readFile,
writeFile,
directoryName,
getDirectories,
createDirectory,
fileExists,
directoryExists,
deleteFile,
listFiles,
getMemoryUsage,
readDirectory,
tryEnableSourceMapsForHost,
getEnvironmentVariable
} = createIO();
+221
View File
@@ -0,0 +1,221 @@
import * as vpath from "./vpath";
import { compareStrings } from "./utils";
export interface IO {
newLine(): string;
useCaseSensitiveFileNames(): boolean;
getCurrentDirectory(): string;
getExecutingFilePath(): string;
getEnvironmentVariable(name: string): string;
args(): string[];
fileExists(path: string): boolean;
directoryExists(path: string): boolean;
getAccessibleFileSystemEntries(path: string): FileSystemEntries;
getDirectories(path: string, options?: { recursive?: boolean, pattern?: RegExp, qualified?: boolean }): string[];
getFiles(path: string, options?: { recursive?: boolean, pattern?: RegExp, qualified?: boolean }): string[];
createDirectory(path: string): void;
readFile(path: string): string | undefined;
writeFile(path: string, contents: string): void;
deleteFile(fileName: string): void;
exit(exitCode?: number): void;
}
export interface FileSystemEntries {
files: string[];
directories: string[];
}
function createNodeIO(): IO {
const fs = require("fs");
const os = require("os");
const platform = os.platform();
const useCaseSensitiveFileNames = isFileSystemCaseSensitive();
const args = process.argv.slice(2);
const executingFilePath = vpath.combine(__dirname, "run.js");
return {
newLine: () => "\r\n",
useCaseSensitiveFileNames: () => useCaseSensitiveFileNames,
getCurrentDirectory: () => process.cwd(),
getExecutingFilePath: () => executingFilePath,
getEnvironmentVariable: name => process.env[name] as string || "",
args: () => args,
fileExists,
directoryExists,
getAccessibleFileSystemEntries,
getFiles,
getDirectories,
createDirectory,
readFile,
writeFile,
deleteFile,
exit
};
function isFileSystemCaseSensitive() {
if (platform === "win32" || <string>platform === "win64") {
return false;
}
return !fileExists(__filename.toUpperCase())
|| !fileExists(__filename.toLowerCase());
}
function fileExists(path: string): boolean {
try {
return fs.statSync(path).isFile() as boolean;
}
catch (e) {
return false;
}
}
function directoryExists(path: string): boolean {
try {
return fs.statSync(path).isDirectory();
}
catch (e) {
return false;
}
}
function getAccessibleFileSystemEntries(dirname: string): FileSystemEntries {
try {
const entries: string[] = fs.readdirSync(dirname || ".").sort(useCaseSensitiveFileNames ? compareStrings.caseSensitive : compareStrings.caseInsensitive);
const files: string[] = [];
const directories: string[] = [];
for (const entry of entries) {
if (entry === "." || entry === "..") continue;
const name = vpath.combine(dirname, entry);
try {
const stat = fs.statSync(name);
if (!stat) continue;
if (stat.isFile()) {
files.push(entry);
}
else if (stat.isDirectory()) {
directories.push(entry);
}
}
catch (e) { }
}
return { files, directories };
}
catch (e) {
return { files: [], directories: [] };
}
}
function getEntries(dirname: string, options: { recursive?: boolean, pattern?: RegExp, qualified?: boolean, kind: "files" | "directories" }): string[] {
const results: string[] = [];
getEntriesWorker(dirname, options.qualified ? dirname : "", options, results);
if (options.recursive) results.sort(compareStrings);
return results;
}
function getEntriesWorker(dirname: string, qualifiedname: string, options: { recursive?: boolean, pattern?: RegExp, kind: "files" | "directories" }, results: string[]) {
const entries = getAccessibleFileSystemEntries(dirname);
const names = entries[options.kind];
for (const name of names) {
if (options.pattern && !options.pattern.test(name)) continue;
results.push(vpath.combine(qualifiedname, name));
}
if (options.recursive) {
for (const name of entries.directories) {
getEntriesWorker(vpath.combine(dirname, name), vpath.combine(qualifiedname, name), options, results);
}
}
}
function getFiles(path: string, options: { recursive?: boolean, pattern?: RegExp, qualified?: boolean } = {}) {
return getEntries(path, { ...options, kind: "files" });
}
function getDirectories(path: string, options: { recursive?: boolean, pattern?: RegExp, qualified?: boolean } = {}) {
return getEntries(path, { ...options, kind: "directories" });
}
function createDirectory(path: string) {
try {
fs.mkdirSync(path);
}
catch (e) {
if (e.code === "ENOENT") {
createDirectory(vpath.dirname(path));
createDirectory(path);
}
else if (!directoryExists(path)) {
throw e;
}
}
}
function readFile(name: string): string | undefined {
try {
const buffer = fs.readFileSync(name);
let len = buffer.length;
if (len >= 2 && buffer[0] === 0xFE && buffer[1] === 0xFF) {
// Big endian UTF-16 byte order mark detected. Since big endian is not supported by node.js,
// flip all byte pairs and treat as little endian.
len &= ~1; // Round down to a multiple of 2
for (let i = 0; i < len; i += 2) {
const temp = buffer[i];
buffer[i] = buffer[i + 1];
buffer[i + 1] = temp;
}
return buffer.toString("utf16le", 2);
}
if (len >= 2 && buffer[0] === 0xFF && buffer[1] === 0xFE) {
// Little endian UTF-16 byte order mark detected
return buffer.toString("utf16le", 2);
}
if (len >= 3 && buffer[0] === 0xEF && buffer[1] === 0xBB && buffer[2] === 0xBF) {
// UTF-8 byte order mark detected
return buffer.toString("utf8", 3);
}
// Default is UTF-8 with no byte order mark
return buffer.toString("utf8");
}
catch (e) {
return undefined;
}
}
function writeFile(name: string, contents: string) {
fs.writeFileSync(name, contents, "utf8");
}
function deleteFile(name: string) {
try {
fs.unlinkSync(name);
}
catch (e) {
}
}
function exit(exitCode?: number) {
process.exit(exitCode);
}
}
function createIO() {
return createNodeIO();
}
export const {
newLine,
useCaseSensitiveFileNames,
getCurrentDirectory,
getExecutingFilePath,
getEnvironmentVariable,
args,
getAccessibleFileSystemEntries,
directoryExists,
getDirectories,
createDirectory,
fileExists,
getFiles,
readFile,
writeFile,
deleteFile,
exit,
} = createIO();
+76
View File
@@ -0,0 +1,76 @@
export interface ParsedArguments {
config?: string;
discover?: boolean;
[option: string]: string | boolean | undefined;
}
interface Option {
name: string;
type: "string" | "boolean";
}
const options: Option[] = [
{ name: "config", type: "string" },
{ name: "discover", type: "boolean" }
];
const optionsMap = new Map<string, Option>(options.map(option => [option.name.toLowerCase(), option] as [string, Option]));
/**
* Regular expression for matching command line arguments.
* Captures:
* 1. An optional `no-` prefix.
* 2. The name of the option.
* 3. An optional quote character (`'` or `"`, used for balancing quotes).
* 4. An optional inline value.
*/
const argRegExp = /^--(no-)?(\w+)(?:=(['"])?(.*)\3)?$/i;
const falseRegExp = /^false$/i;
export function parseCommandLine(args: string[]) {
const parsedArgs: ParsedArguments = { };
for (let i = 0; i < args.length; i++) {
const match = argRegExp.exec(args[i]);
if (match) {
const [/*all*/, no, name, /*quote*/, value] = match;
const opt = optionsMap.get(name.toLowerCase());
if (opt) {
switch (opt.type) {
case "string":
if (no) {
// `--no-` not supported on strings
break;
}
let stringValue: string;
if (value) {
stringValue = value;
}
else if (i < args.length - 1) {
i++;
stringValue = args[i];
}
else {
// string options require a value
break;
}
parsedArgs[opt.name] = stringValue;
break;
case "boolean":
let booleanValue = true;
if (value) {
booleanValue = !falseRegExp.test(value);
}
if (no) {
booleanValue = !booleanValue;
}
parsedArgs[opt.name] = booleanValue;
break;
}
}
}
}
return parsedArgs;
}
+187
View File
@@ -0,0 +1,187 @@
/// <reference types="mocha" />
import * as io from "./io";
import * as vpath from "./vpath";
import * as ts from "./api";
import { TestRunTask, TestRunnerKind } from "./runner";
import { getTestConfig, TestConfig } from "./config";
import { parseCommandLine } from "./options";
import { getExecutionEnvironment, ExecutionEnvironment } from "./utils";
import { CompilerRunner } from "./runners/compiler";
import { install } from "source-map-support";
// enable source
install();
// start harness
main();
/**
* Get tasks that describe the test runners and test cases to use in this session.
*/
function getTestRunTasks(config: TestConfig): TestRunTask[] {
const runners: TestRunTask[] = [];
// If `tasks` have been specified, add each task
if (config.tasks) {
for (const testRun of config.tasks) {
runners.push(testRun);
}
}
// if `test` has been specified, add each test suite.
if (config.test) {
for (const option of config.test) {
switch (option) {
case "compiler":
runners.push({ runner: "conformance" });
runners.push({ runner: "compiler" });
// runners.push({ runner: "project" });
break;
case "conformance":
runners.push({ runner: "conformance" });
break;
// case "project":
// runners.push({ runner: "project" });
// break;
// case "fourslash":
// runners.push({ runner: "fourslash" });
// break;
// case "fourslash-shims":
// runners.push({ runner: "fourslash-shims" });
// break;
// case "fourslash-shims-pp":
// runners.push({ runner: "fourslash-shims-pp" });
// break;
// case "fourslash-server":
// runners.push({ runner: "fourslash-server" });
// break;
// case "fourslash-generated":
// runners.push({ runner: "fourslash-generated" });
// break;
// case "rwc":
// runners.push({ runner: "rwc" });
// break;
// case "test262":
// runners.push({ runner: "test262" });
// break;
}
}
}
// if nothing has been specified, add defaults.
if (runners.length === 0) {
// compiler
runners.push({ runner: "conformance" });
runners.push({ runner: "compiler" });
// TODO: project tests don't work in the browser yet
if (getExecutionEnvironment() !== ExecutionEnvironment.Browser) {
// runners.push({ runner: "project" });
}
// language services
// runners.push({ runner: "fourslash" });
// runners.push({ runner: "fourslash-shims" });
// runners.push({ runner: "fourslash-shims-pp" });
// runners.push({ runner: "fourslash-server" });
}
return runners;
}
/**
* Creates a test runner for the provided kind.
* @param kind The kind of runner to create
* @param config The test harness configuration
*/
function createRunner(kind: TestRunnerKind, config: TestConfig) {
switch (kind) {
case "conformance": return new CompilerRunner(kind, config);
case "compiler": return new CompilerRunner(kind, config);
}
}
/**
* Divides the tasks in this session into one or more partitions to be run in parallel.
* Tests discovered are distributed between partitions as evenly as possible, with the exception
* that the last partition will also run the unit tests.
* @param taskConfigsFolder The folder into which test partition configuration files should be written.
* @param tasks The tasks for the session that should be partitioned.
*/
function discoverTests(config: TestConfig, tasks: TestRunTask[]) {
if (!config.taskConfigsFolder) throw new Error("Property 'taskConfigsFolder' not specified in config.");
if (!config.workerCount) throw new Error("Property 'workerCount' not specified in config.");
// Create partitions
const partitions: TestConfig[] = [];
for (let i = 0; i < config.workerCount; i++) {
// Use the last worker to run unit tests. This may need to be changed in the future...
partitions.push({ light: config.light, tasks: [], runUnitTests: i === config.workerCount - 1 });
}
// Add tasks to each partition
for (const task of tasks) {
const runner = createRunner(task.runner, config);
const tests = runner.discover();
const remainder = tests.length % config.workerCount;
const quotient = (tests.length - remainder) / config.workerCount;
let end = -1;
for (let i = 0; i < config.workerCount; i++) {
// We know that `tasks` is defined as we provided it above.
const tasks = partitions[i].tasks!;
const start = end + 1;
end = i < remainder ? start + quotient : start + quotient - 1;
tasks.push({ runner: runner.kind, tests: tests.slice(start, end) });
}
}
// Write each partition configuration.
for (let i = 0; i < config.workerCount; i++) {
io.writeFile(vpath.combine(config.taskConfigsFolder, `task-config${i}.json`), JSON.stringify(partitions[i]));
}
}
/**
* Runs the provided tests.
* @param tasks The tasks for the session that should be run.
*/
function runTests(config: TestConfig, tasks: TestRunTask[]) {
// Set stack trace limit
if (config.stackTraceLimit === "full") {
Error.stackTraceLimit = Infinity;
}
else if (config.stackTraceLimit !== undefined) {
Error.stackTraceLimit = config.stackTraceLimit;
}
// Enable debugging support
if (ts.Debug.isDebugging) {
ts.Debug.enableDebugInfo();
}
// Run tests
for (const task of tasks) {
const runner = createRunner(task.runner, config);
runner.run(task.tests);
}
}
/**
* Main entrypoint for the harness.
*/
function main() {
const args = parseCommandLine(io.args());
const config = getTestConfig(args);
const tasks = getTestRunTasks(config);
if (args.discover) {
discoverTests(config, tasks);
}
else {
runTests(config, tasks);
}
if (!config.runUnitTests) {
// patch `describe` to skip unit tests
// FIXME(rbuckton): We need to have a better way to handle this.
(<any>global).describe = function () { };
}
}
+74
View File
@@ -0,0 +1,74 @@
import { TestConfig } from "./config";
export type TestRunnerKind = "conformance" | "compiler";
export interface TestRunTask {
/**
* The id of the associated runner.
*/
runner: TestRunnerKind;
/**
* The ids of the test cases in the runner.
*/
tests?: string[];
}
export abstract class Runner<TKind extends TestRunnerKind = TestRunnerKind> {
public readonly kind: TKind;
public readonly config: TestConfig;
constructor(kind: TKind, config: TestConfig) {
this.kind = kind;
this.config = config;
}
/**
* Discover test cases for the runner.
*/
public abstract discover(): string[];
/**
* Setup the runner's tests so that they are ready to be executed by the harness.
* @param tests The tests for this run.
*/
public run(tests = this.discover()): void {
describe(`${this.kind} tests`, () => {
if (this.before !== Runner.prototype.before) before(() => this.before());
if (this.beforeEach !== Runner.prototype.beforeEach) beforeEach(() => this.beforeEach());
if (this.afterEach !== Runner.prototype.afterEach) afterEach(() => this.afterEach());
if (this.after !== Runner.prototype.after) after(() => this.after());
for (const test of tests) {
describe(`${this.kind} tests for ${test}`, () => {
this.runSuite(test);
});
}
});
}
/**
* Override to perform initialization before any tests in the runner are executed.
*/
protected before(): void { }
/**
* Override to perform initialization before each test in the runner is executed.
*/
protected beforeEach(): void { }
/**
* Override to perform cleanup after any tests in the runner are executed.
*/
protected after(): void {}
/**
* Override to perform cleanup after each test in the runner is executed.
*/
protected afterEach(): void { }
/**
* Override to describe test suites and tests for a specific test case.
* @param id The id of the test case.
*/
protected abstract runSuite(id: string): void;
}
+299
View File
@@ -0,0 +1,299 @@
import * as vpath from "../vpath";
import * as io from "../io";
import * as ts from "../api";
import { Runner } from "../runner";
import { TextDocument, isDeclarationDocument, isTypeScriptDocument } from "../documents";
import { VirtualFileSystem } from "../vfs";
import { parseTestCase, TestCaseOptions } from "../testCaseParser";
import { compileFiles, CompilationResult, ParseConfigHost } from "../compiler";
import { assert } from "chai";
import { baseline } from "../baselines";
import { formatDiagnostics, formatJavaScript, formatSourceMaps, formatTypes, formatSymbols, formatSourceMapData, formatModuleResolution } from "../formatters";
import { isJsonFile, compareStrings } from "../utils";
export class CompilerRunner extends Runner<"conformance" | "compiler"> {
private _basePath: string | undefined;
public get basePath() {
return this._basePath || (this._basePath = vpath.combine("tests/cases", this.kind));
}
// nee. enumerateTestFiles()
public discover(): string[] {
return io.getFiles(this.basePath, { recursive: true, pattern: /\.tsx?$/, qualified: true });
}
// nee. initializeTests()
protected runSuite(test: string): void {
let compilerTest: CompilerTest | undefined;
before(() => compilerTest = new CompilerTest(this, test));
// it("errors", () => compilerTest && compilerTest.verifyDiagnostics());
// it("module resolution", () => compilerTest && compilerTest.verifyModuleResolution());
// it("output", () => compilerTest && compilerTest.verifyJavaScriptOutput());
it("sourcemap record", () => compilerTest && compilerTest.verifySourceMapRecord());
// it("sourcemap", () => compilerTest && compilerTest.verifySourceMapOutput());
// it("types", () => compilerTest && compilerTest.verifyTypes());
// it("symbols", () => compilerTest && compilerTest.verifySymbols());
after(() => compilerTest = undefined);
}
}
class CompilerTest {
private runner: CompilerRunner;
private basename: string;
private document: TextDocument;
private documents: TextDocument[];
private configDocument: TextDocument | undefined;
private meta: Map<string, string>;
private config: ts.ParsedCommandLine | undefined;
private options: TestCaseOptions;
private vfs: VirtualFileSystem;
private rootFiles: string[];
private rootDocuments: TextDocument[];
private nonRootDocuments: TextDocument[];
private result: CompilationResult;
private declarationVfs: VirtualFileSystem | undefined;
private declarationRootFiles: string[] | undefined;
private declarationDocuments: TextDocument[] | undefined;
private declarationResult: CompilationResult | undefined;
private hasNonDeclarationFiles = false;
private typesAndSymbolsDocuments: TextDocument[] | undefined;
private typesAndSymbols: Map<string, ts.TypesAndSymbols[]> | undefined;
constructor(runner: CompilerRunner, file: string) {
this.runner = runner;
this.basename = vpath.basename(file);
this.document = new TextDocument(file, io.readFile(file) || "");
const { documents, options, meta } = parseTestCase(this.document);
this.meta = meta;
this.options = options;
if (options.useCaseSensitiveFileNames === undefined) options.useCaseSensitiveFileNames = true;
if (options.noTypesBaseline === undefined) options.noTypesBaseline = false;
if (options.noSymbolsBaseline === undefined) options.noSymbolsBaseline = false;
// TODO: @baseUrl - May not be needed due to the use of the vfs.
// TODO: @baselineFile
// FIXME(rbuckton): The old harness would effectively overwrite a previously
// declared file that shares the same name. This really should be an error.
this.vfs = VirtualFileSystem.createFromDocuments(this.options, documents, { overwrite: true });
const prepared = prepareDocuments(this.options, documents);
this.documents = prepared.documents;
this.configDocument = prepared.configDocument;
this.rootFiles = prepared.rootFiles;
this.rootDocuments = prepared.rootDocuments;
this.nonRootDocuments = prepared.nonRootDocuments;
this.hasNonDeclarationFiles = prepared.hasNonDeclarationFiles;
const noImplicitReferences = prepared.noImplicitReferences;
if (this.configDocument) {
const { config } = ts.parseConfigFileTextToJson(this.configDocument.file, this.configDocument.text);
assert.isDefined(config);
const baseDir = vpath.dirname(this.configDocument.file);
const host = new ParseConfigHost(this.vfs);
this.config = ts.parseJsonConfigFileContent(config, host, baseDir, /*existingOptions*/ undefined, this.configDocument.file);
this.options = { ...this.config.options, ...this.options };
}
else {
if (this.options.noResolve === undefined) this.options.noResolve = false;
}
this.result = compileFiles(this.vfs, "/.ts", this.rootFiles, this.options);
// check declaration files
if (this.hasNonDeclarationFiles && this.options.declaration && this.result.diagnostics.length === 0 && this.result.dts.size > 0) {
const declarationOptions = { ...this.options, declaration: false, noImplicitReferences };
const declarationDocuments: TextDocument[] = [];
for (const document of documents) {
if (isDeclarationDocument(document) || !isTypeScriptDocument(document)) {
declarationDocuments.push(document);
}
const outputs = this.result.getInputsAndOutputs(document.file);
const dts = outputs && outputs.dts;
if (dts) {
declarationDocuments.push(dts);
}
}
// FIXME(rbuckton): The old harness would effectively overwrite a previously
// declared file that shares the same name. This really should be an error.
this.declarationVfs = VirtualFileSystem.createFromDocuments(declarationOptions, declarationDocuments, { overwrite: true });
const prepared = prepareDocuments(declarationOptions, declarationDocuments);
this.declarationDocuments = prepared.documents;
this.declarationRootFiles = prepared.rootFiles;
this.declarationResult = compileFiles(this.declarationVfs, "/.ts", this.declarationRootFiles, declarationOptions);
}
// walk types and symbols
if (this.result.diagnostics.length === 0 && (!this.options.noTypesBaseline || !this.options.noSymbolsBaseline)) {
// The full walker simulates the types that you would get from doing a full
// compile. The pull walker simulates the types you get when you just do
// a type query for a random node (like how the LS would do it). Most of the
// time, these will be the same. However, occasionally, they can be different.
// Specifically, when the compiler internally depends on symbol IDs to order
// things, then we may see different results because symbols can be created in a
// different order with 'pull' operations, and thus can produce slightly differing
// output.
//
// For example, with a full type check, we may see a type displayed as: number | string
// But with a pull type check, we may see it as: string | number
//
// These types are equivalent, but depend on what order the compiler observed
// certain parts of the program.
const exclude = this.options.noTypesBaseline ? "types" : this.options.noSymbolsBaseline ? "symbols" : undefined;
this.typesAndSymbols = new Map<string, ts.TypesAndSymbols[]>();
this.typesAndSymbolsDocuments = [];
for (const document of this.documents) {
if (!this.result.program.getSourceFile(document.file)) continue;
const typesAndSymbols = ts.getTypesAndSymbols(this.program, document.file, /*checked*/ true, exclude);
this.typesAndSymbols.set(document.file, typesAndSymbols);
this.typesAndSymbolsDocuments.push(document);
}
}
}
private get isEmitSkipped() {
return this.options.noEmitOnError && this.result.diagnostics.length > 0;
}
private get program() {
return this.result.program;
}
public verifyDiagnostics(): void {
const hasContent = this.result.diagnostics.length > 0;
const content = hasContent ? formatDiagnostics(this.documents, this.result) : undefined;
baseline(vpath.chext(this.basename, ".errors.txt"), content);
}
public verifyModuleResolution(): void {
if (!this.options.traceResolution) return;
const content = formatModuleResolution(this.result);
baseline(vpath.chext(this.basename, ".trace.json"), content);
}
public verifySourceMapRecord(): void {
if (!this.options.sourceMap && !this.options.inlineSourceMap) return;
const content = this.isEmitSkipped ? undefined : formatSourceMapData(this.result);
baseline(vpath.chext(this.basename, ".sourcemap.txt"), content);
}
public verifyJavaScriptOutput(): void {
if (!this.hasNonDeclarationFiles) return;
assert.isOk(this.options.noEmit || this.result.js.size || this.result.diagnostics.length, "Expected at least one js file to be emitted or at least one error to be created.");
assert.isOk(!this.options.declaration || this.result.diagnostics.length > 0 || this.result.dts.size === this.result.js.size, "There were no errors and declFiles generated did not match number of js files generated.");
const hasContent = this.result.js.size > 0 || this.result.dts.size > 0 || (this.declarationResult && this.declarationResult.diagnostics.length > 0);
let content: string | undefined;
if (hasContent) {
content = formatJavaScript(
this.document.file,
this.options.fullEmitPaths || false,
this.nonRootDocuments.concat(this.rootDocuments), // NOTE: The previous harness emits non-root documents before root documents.
this.result,
this.declarationDocuments && [...this.declarationDocuments, ...this.nonRootDocuments],
this.declarationResult
);
}
baseline(vpath.chext(this.basename, ".js"), content);
}
public verifySourceMapOutput(): void {
if (this.options.inlineSourceMap) {
assert.equal(this.result.maps.size, 0, "No sourcemap files should be generated if inlineSourceMaps was set.");
return;
}
if (!this.options.sourceMap) return;
assert.equal(this.result.maps.size, this.result.js.size, "Number of sourcemap files should be same as js files.");
const hasContent = !this.isEmitSkipped && this.result.maps.size > 0;
const content = hasContent ? formatSourceMaps(this.options.fullEmitPaths || false, this.result) : undefined;
baseline(vpath.chext(this.basename, ".js.map"), content);
}
public verifyTypes(): void {
if (this.options.noTypesBaseline || !this.typesAndSymbols || !this.typesAndSymbolsDocuments || this.result.diagnostics.length > 0) return;
const content = formatTypes(this.typesAndSymbolsDocuments, this.typesAndSymbols);
baseline(vpath.chext(this.basename, ".types"), content);
}
public verifySymbols(): void {
if (this.options.noSymbolsBaseline || !this.typesAndSymbols || !this.typesAndSymbolsDocuments || this.result.diagnostics.length > 0) return;
const content = formatSymbols(this.typesAndSymbolsDocuments, this.typesAndSymbols);
baseline(vpath.chext(this.basename, ".symbols"), content);
}
}
function prepareDocuments(options: TestCaseOptions, documents: TextDocument[]) {
const rootFiles: string[] = [];
const rootDocuments: TextDocument[] = [];
const nonRootDocuments: TextDocument[] = [];
const allDocuments: TextDocument[] = [];
let configDocument: TextDocument | undefined;
let hasNonDeclarationFiles = false;
// FIXME(rbuckton): This is a mildly frustrating and esoteric feature of our test harness.
// We blindly assume that if the last document contains a call to `require` or a
// <reference /> directive then we do not want implicit references. We instead need to
// find a way to be more explicit about this.
let lastDocument = documents[documents.length - 1];
if (lastDocument && vpath.basename(lastDocument.file) === "tsconfig.json" && documents.length > 1) {
lastDocument = documents[documents.length - 2];
}
const noImplicitReferences = options.noImplicitReferences || /require\(/.test(lastDocument.text) || /reference\spath/.test(lastDocument.text);
// FIXME(rbuckton): Odd ordering required to be compatible with existing harness
if (noImplicitReferences) {
allDocuments.push(lastDocument);
rootFiles.push(lastDocument.file);
rootDocuments.push(lastDocument);
}
// Add documents
for (const document of documents) {
const basename = vpath.basename(document.file);
if (compareStrings(basename, "tsconfig.json", !options.useCaseSensitiveFileNames) === 0) {
if (!configDocument) {
configDocument = document;
}
}
else if (isJsonFile(basename)) {
allDocuments.push(document);
nonRootDocuments.push(document);
}
else {
if (!vpath.extname(document.file, { extensions: [".d.ts"] })) {
hasNonDeclarationFiles = true;
}
// FIXME(rbuckton): Odd ordering required to be compatible with existing harness
if (noImplicitReferences) {
if (document.file === lastDocument.file) continue;
nonRootDocuments.push(document);
}
else {
rootFiles.push(document.file);
rootDocuments.push(document);
}
allDocuments.push(document);
}
}
if (options.includeBuiltFile) {
rootFiles.push(vpath.combine("/.ts", options.includeBuiltFile));
}
if (options.libFiles) {
for (const libFile of options.libFiles) {
rootFiles.push(vpath.combine("/.lib", libFile));
}
}
return { rootFiles, rootDocuments, nonRootDocuments, documents: allDocuments, configDocument, hasNonDeclarationFiles, noImplicitReferences };
}
+164
View File
@@ -0,0 +1,164 @@
export interface RawSourceMap {
version: number;
file: string;
sourceRoot?: string;
sources: string[];
sourcesContent?: string[];
names: string[];
mappings: string;
}
export interface Source {
file: string;
sourceIndex: number;
content?: string;
}
export interface Mapping {
emittedLine: number;
emittedColumn: number;
source: Source;
sourceIndex: number;
sourceLine: number;
sourceColumn: number;
nameIndex?: number;
name?: string;
}
const mappingRegExp = /([A-Za-z0-9+/]+),?|(;)|./g;
const sourceMappingURLRegExp = /^\/\/[#@]\s*sourceMappingURL\s*=\s*(.*?)\s*$/mig;
const dataURLRegExp = /^data:application\/json;base64,([a-z0-9+/=]+)$/i;
export class SourceMap {
public readonly mapFile: string | undefined;
public readonly sourceMap: RawSourceMap;
public readonly version: number;
public readonly file: string;
public readonly sourceRoot: string | undefined;
public readonly sources: ReadonlyArray<Source> = [];
public readonly mappings: ReadonlyArray<Mapping> = [];
public readonly names: ReadonlyArray<string>;
private _emittedLineMappings: Mapping[][] = [];
private _sourceLineMappings: Mapping[][][] = [];
constructor(mapFile: string | undefined, text: string) {
this.mapFile = mapFile;
this.sourceMap = JSON.parse(text);
this.version = this.sourceMap.version;
this.file = this.sourceMap.file;
this.sourceRoot = this.sourceMap.sourceRoot;
// populate sources
const sources: Source[] = [];
for (let i = 0; i < this.sourceMap.sources.length; i++) {
const source: Source = { file: this.sourceMap.sources[i], sourceIndex: i };
if (this.sourceMap.sourcesContent) {
source.content = this.sourceMap.sourcesContent[i];
}
sources.push(source);
}
this.sources = sources;
// populate names
this.names = this.sourceMap.names && this.sourceMap.names.slice() || [];
// populate mappings
const mappings: Mapping[] = [];
let emittedLine = 0;
let emittedColumn = 0;
let sourceIndex = 0;
let sourceLine = 0;
let sourceColumn = 0;
let nameIndex = 0;
let match: RegExpExecArray | null;
while (match = mappingRegExp.exec(this.sourceMap.mappings)) {
if (match[1]) {
const segment = decodeVLQ(match[1]);
if (segment.length !== 1 && segment.length !== 4 && segment.length !== 5) {
throw new Error("Invalid VLQ");
}
emittedColumn += segment[0];
if (segment.length >= 4) {
sourceIndex += segment[1];
sourceLine += segment[2];
sourceColumn += segment[3];
}
const mapping: Mapping = { emittedLine, emittedColumn, source: this.sources[sourceIndex], sourceIndex, sourceLine, sourceColumn };
if (segment.length === 5) {
nameIndex += segment[4];
mapping.nameIndex = nameIndex;
mapping.name = this.names[nameIndex];
}
mappings.push(mapping);
const mappingsForEmittedLine = this._emittedLineMappings[mapping.emittedLine] || (this._emittedLineMappings[mapping.emittedLine] = []);
mappingsForEmittedLine.push(mapping);
const mappingsForSource = this._sourceLineMappings[mapping.sourceIndex] || (this._sourceLineMappings[mapping.sourceIndex] = []);
const mappingsForSourceLine = mappingsForSource[mapping.sourceLine] || (mappingsForSource[mapping.sourceLine] = []);
mappingsForSourceLine.push(mapping);
}
else if (match[2]) {
emittedLine++;
emittedColumn = 0;
}
else {
throw new Error(`Unrecognized character '${match[0]}'.`);
}
}
this.mappings = mappings;
}
public static getUrl(text: string) {
let match: RegExpExecArray | null;
let lastMatch: RegExpExecArray | undefined;
while (match = sourceMappingURLRegExp.exec(text)) {
lastMatch = match;
}
return lastMatch ? lastMatch[1] : undefined;
}
public static fromUrl(url: string) {
const match = dataURLRegExp.exec(url);
return match ? new SourceMap(/*mapFile*/ undefined, new Buffer(match[1], "base64").toString("utf8")) : undefined;
}
public static fromSource(text: string) {
const url = this.getUrl(text);
return url && this.fromUrl(url);
}
public getMappingsForEmittedLine(emittedLine: number): ReadonlyArray<Mapping> | undefined {
return this._emittedLineMappings[emittedLine];
}
public getMappingsForSourceLine(sourceIndex: number, sourceLine: number): ReadonlyArray<Mapping> | undefined {
const mappingsForSource = this._sourceLineMappings[sourceIndex];
return mappingsForSource && mappingsForSource[sourceLine];
}
}
const base64Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
export function decodeVLQ(text: string) {
const vlq: number[] = [];
let shift = 0;
let value = 0;
for (let i = 0; i < text.length; i++) {
const currentByte = base64Chars.indexOf(text.charAt(i));
value += (currentByte & 31) << shift;
if ((currentByte & 32) === 0) {
vlq.push(value & 1 ? -(value >>> 1) : value >>> 1);
shift = 0;
value = 0;
}
else {
shift += 5;
}
}
return vlq;
}
+159
View File
@@ -0,0 +1,159 @@
import { TextDocument } from "./documents";
import { TextWriter } from "./textWriter";
import * as ts from "./api";
import * as vpath from "./vpath";
import { getLinesAndLineStarts } from "./utils";
const optionRegExp = /^\/{2}\s*@(\w+)\s*:\s*(.*?)\s*$/;
export interface TestCaseOptions extends ts.CompilerOptions {
allowNonTsExtensions?: boolean;
baselineFile?: string;
currentDirectory?: string;
fullEmitPaths?: boolean;
includeBuiltFile?: string;
libFiles?: string[];
noErrorTruncation?: boolean;
noImplicitReferences?: boolean;
noSymbolsBaseline?: boolean;
noTypesBaseline?: boolean;
useCaseSensitiveFileNames?: boolean;
}
export interface TestCaseParseResult {
meta: Map<string, any>;
options: TestCaseOptions;
documents: TextDocument[];
}
const testCaseOptions: ts.CommandLineOption[] = [
{ name: "allowNonTsExtensions", type: "boolean" },
{ name: "baselineFile", type: "string" },
{ name: "currentDirectory", type: "string" },
{ name: "fullEmitPaths", type: "boolean" }, // Emitted js baseline will print full paths for every output file
{ name: "includeBuiltFile", type: "string" },
{ name: "libFiles", type: "list", element: { name: "libFiles", type: "string" } },
{ name: "suppressOutputPathCheck", type: "boolean" },
{ name: "noErrorTruncation", type: "boolean" },
{ name: "noImplicitReferences", type: "boolean" },
{ name: "noSymbolsBaseline", type: "boolean" },
{ name: "noTypesBaseline", type: "boolean" },
{ name: "useCaseSensitiveFileNames", type: "boolean" },
];
let testCaseOptionsMap: Map<string, ts.CommandLineOption> | undefined;
function getCommandLineOption(name: string) {
if (!testCaseOptionsMap) {
testCaseOptionsMap = new Map<string, ts.CommandLineOption>();
for (const opt of testCaseOptions) {
testCaseOptionsMap.set(opt.name.toLowerCase(), opt);
}
for (const opt of ts.optionDeclarations) {
testCaseOptionsMap.set(opt.name.toLowerCase(), opt);
}
}
return testCaseOptionsMap.get(name.toLowerCase());
}
function parseOption(opt: ts.CommandLineOption, value: string, diagnostics: ts.Diagnostic[]) {
switch (opt.type) {
case "boolean":
return value.toLowerCase() === "true";
case "string":
return value;
case "number":
const number = parseInt(value, 10);
if (isNaN(number)) throw new Error(`Value must be a number, got: ${JSON.stringify(value)}`);
return number;
case "list":
return ts.parseListTypeOption(<ts.CommandLineOptionOfListType>opt, value, diagnostics);
default:
return ts.parseCustomTypeOption(<ts.CommandLineOptionOfCustomType>opt, value, diagnostics);
}
}
export function parseTestCaseOptions(map: Map<string, string>) {
const options: TestCaseOptions = { };
const diagnostics: ts.Diagnostic[] = [];
map.forEach((value, key) => {
const opt = getCommandLineOption(key);
if (!opt) throw new Error(`Unknown compiler option '${key}'.`);
options[opt.name] = parseOption(opt, value, diagnostics);
if (diagnostics.length > 0) throw new Error(`Unrecognized value '${value}' for compiler option '${key}'.`);
});
return options;
}
function isDocumentMetadata(key: string) {
return key === "symlink";
}
export function parseTestCase(document: TextDocument): TestCaseParseResult {
const text = document.text;
const meta = new Map<string, string>();
const documents: TextDocument[] = [];
const dirname = vpath.dirname(document.file);
// NOTE: Old compiler is inconsistent as it uses `\n` instead of `\r\n` here.
const writer = new TextWriter(/*text*/ undefined, { eol: "\n" });
let documentName: string | undefined;
let documentMeta: Map<string, string> | undefined;
let preserveLines = false;
let needsSeparator = false;
const { lines, lineStarts } = getLinesAndLineStarts(text);
for (let i = 0; i < lineStarts.length; i++) {
const line = lines[i];
const match = optionRegExp.exec(line);
if (match) {
const key = match[1].trim();
const keyLower = key.toLowerCase();
const value = match[2].trim();
if (keyLower === "filename") {
// add previous document
if (documentName && documentMeta) {
documents.push(new TextDocument(documentName, writer.toString(), documentMeta));
writer.clear();
needsSeparator = false;
}
// start new document
documentName = vpath.combine(dirname, value);
documentMeta = new Map<string, string>();
documentMeta.set("filename", documentName);
}
else if (keyLower === "preservelines") {
preserveLines = value.toLowerCase() === "true";
}
else if (isDocumentMetadata(keyLower)) {
if (documentMeta) {
documentMeta.set(key, value);
}
}
else {
meta.set(key, value);
}
}
else {
if (needsSeparator) {
writer.writeln();
}
if (preserveLines) {
const lineStart = lineStarts[i];
const lineEnd = i < lineStarts.length - 1 ? lineStarts[i + 1] : text.length;
writer.write(text.slice(lineStart, lineEnd));
if (needsSeparator && writer.size > 0) {
needsSeparator = false;
}
}
else {
writer.write(line);
if (!needsSeparator && writer.size > 0) {
needsSeparator = true;
}
}
}
}
// Add remaining document
documents.push(new TextDocument(documentName || document.file, writer.toString(), documentMeta || meta));
return { meta, options: parseTestCaseOptions(meta), documents };
}
+60
View File
@@ -0,0 +1,60 @@
export class TextWriter {
private text: string;
private eol: string;
private indents = ["", " "];
private indentDepth = 0;
private indentRequested = true;
constructor(text?: string, options?: { eol?: string }) {
this.text = text || "";
this.eol = options && options.eol || "\r\n";
}
public get size() {
return this.text.length;
}
public write(text?: string) {
if (text) {
this.writeIndent();
this.text += text;
}
return this;
}
public writeln(text?: string) {
this.write(text);
this.text += this.eol;
this.indentRequested = true;
return this;
}
public increaseIndent() {
this.indentDepth++;
return this;
}
public decreaseIndent() {
this.indentDepth = Math.max(0, this.indentDepth - 1);
return this;
}
public toString() {
return this.text;
}
public clear() {
this.text = "";
this.indentRequested = true;
this.indentDepth = 0;
}
private writeIndent() {
if (!this.indentRequested) return;
while (this.indents.length <= this.indentDepth) {
this.indents[this.indents.length] = this.indents[this.indents.length - 1] + this.indents[1];
}
if (this.indentDepth) this.text += this.indents[this.indentDepth];
this.indentRequested = false;
}
}
+43
View File
@@ -0,0 +1,43 @@
{
"extends": "../tsconfig-base",
"compilerOptions": {
"removeComments": false,
"outDir": "../../built/harness/",
"module": "commonjs",
"declaration": false,
"types": [
"node",
"mocha",
"chai"
],
"lib": [
"es6",
"scripthost"
],
"strict": true
},
"files": [
"utils.ts",
"collections.ts",
"vpath.ts",
"vfs.ts",
"io.ts",
"options.ts",
"documents.ts",
"testCaseParser.ts",
"textWriter.ts",
"sourceMaps.ts",
"baselines.ts",
"compiler.ts",
"formatters/index.ts",
"formatters/diagnostics.ts",
"formatters/javaScript.ts",
"formatters/moduleResolution.ts",
"formatters/sourceMaps.ts",
"formatters/types.ts",
"formatters/symbols.ts",
"runner.ts",
"runners/compiler.ts",
"run.ts"
]
}
+320
View File
@@ -0,0 +1,320 @@
import * as vpath from "./vpath";
declare var window: any;
export const enum ExecutionEnvironment {
Node,
Browser,
}
export function getExecutionEnvironment() {
if (typeof window !== "undefined") {
return ExecutionEnvironment.Browser;
}
else {
return ExecutionEnvironment.Node;
}
}
let executionDirectory: string | undefined;
export function getExecutionDirectory() {
return executionDirectory || (executionDirectory = vpath.resolve(__dirname, "../../"));
}
export function getBuiltDirectory() {
return vpath.combine(getExecutionDirectory(), "built/local");
}
export function getLibFilesDirectory() {
return vpath.combine(getExecutionDirectory(), "tests/lib");
}
//
// String Utilities
//
export function toUtf8(text: string): string {
return new Buffer(text).toString("utf8");
}
export function compareValues<T>(a: T, b: T) {
if (a === b) return 0;
if (a === undefined || a === null) return -1; // tslint:disable-line:no-null-keyword
if (b === undefined || b === null) return +1; // tslint:disable-line:no-null-keyword
return a < b ? -1 : a > b ? +1 : 0;
}
const caseInsensitiveComparisonCollator = typeof Intl === "object" ? new Intl.Collator(/*locales*/ undefined, { usage: "sort", sensitivity: "accent" }) : undefined;
const caseSensitiveComparisonCollator = typeof Intl === "object" ? new Intl.Collator(/*locales*/ undefined, { usage: "sort", sensitivity: "variant" }) : undefined;
export function compareStrings(a: string | undefined, b: string | undefined, ignoreCase?: boolean) {
if (a === b) return 0;
if (a === undefined) return -1;
if (b === undefined) return +1;
const collator = ignoreCase ? caseInsensitiveComparisonCollator : caseSensitiveComparisonCollator;
if (collator) {
return collator.compare(a, b);
}
else if (ignoreCase) {
a = a.toUpperCase();
b = b.toUpperCase();
}
return a < b ? -1 : a > b ? +1 : 0;
}
export namespace compareStrings {
export function caseSensitive(a: string | undefined, b: string | undefined) {
return compareStrings(a, b, /*ignoreCase*/ false);
}
export function caseInsensitive(a: string | undefined, b: string | undefined) {
return compareStrings(a, b, /*ignoreCase*/ true);
}
}
export function padLeft(text: string, size: number, ch = " ") {
while (text.length < size) text = ch + text;
return text;
}
export function padRight(text: string, size: number, ch = " ") {
while (text.length < size) text += ch;
return text;
}
export function repeatString(text: string, count: number) {
let result = "";
for (let i = 0; i < count; i++) {
result += text;
}
return result;
}
function splitLinesWorker(text: string, lines: string[] | undefined, removeEmptyElementsOrLineStarts: boolean | number[] | undefined) {
const lineStarts = typeof removeEmptyElementsOrLineStarts !== "boolean" ? removeEmptyElementsOrLineStarts : undefined;
const removeEmptyElements = typeof removeEmptyElementsOrLineStarts === "boolean" ? removeEmptyElementsOrLineStarts : undefined;
let pos = 0;
let end = 0;
let lineStart = 0;
let nonWhiteSpace = false;
while (pos < text.length) {
const ch = text.charCodeAt(pos);
end = pos;
pos++;
switch (ch) {
// LineTerminator
case 0x000d: // <CR> carriage return
if (pos < text.length && text.charCodeAt(pos) === 0x000a) pos++;
// falls through
case 0x000a: // <LF> line feed
case 0x2028: // <LS> line separator
case 0x2029: // <PS> paragraph separator
if (lineStarts) {
lineStarts.push(lineStart);
}
if (lines && (!removeEmptyElements || nonWhiteSpace)) {
lines.push(text.slice(lineStart, end));
}
lineStart = pos;
nonWhiteSpace = false;
break;
// WhiteSpace
case 0x0009: // <TAB> tab
case 0x000b: // <VT> vertical tab
case 0x000c: // <FF> form feed
case 0x0020: // <SP> space
case 0x00a0: // <NBSP> no-break space
case 0xfeff: // <ZWNBSP> zero width no-break space
case 0x1680: // <USP> ogham space mark
case 0x2000: // <USP> en quad
case 0x2001: // <USP> em quad
case 0x2002: // <USP> en space
case 0x2003: // <USP> em space
case 0x2004: // <USP> three-per-em space
case 0x2005: // <USP> four-per-em space
case 0x2006: // <USP> six-per-em space
case 0x2007: // <USP> figure space
case 0x2008: // <USP> punctuation space
case 0x2009: // <USP> thin space
case 0x200a: // <USP> hair space
case 0x202f: // <USP> narrow no-break space
case 0x205f: // <USP> medium mathematical space
case 0x3000: // <USP> ideographic space
case 0x0085: // next-line (not strictly per spec, but used by the compiler)
break;
default:
nonWhiteSpace = true;
break;
}
}
if (lineStarts) {
lineStarts.push(lineStart);
}
if (lines && (!removeEmptyElements || nonWhiteSpace)) {
lines.push(text.slice(lineStart, text.length));
}
}
export function getLinesAndLineStarts(text: string) {
const lines: string[] = [];
const lineStarts: number[] = [];
splitLinesWorker(text, lines, lineStarts);
return { lines, lineStarts };
}
export function splitLines(text: string, removeEmptyElements?: boolean): string[] {
const lines: string[] = [];
splitLinesWorker(text, lines, removeEmptyElements);
return lines;
}
export function computeLineStarts(text: string): number[] {
const lineStarts: number[] = [];
splitLinesWorker(text, /*lines*/ undefined, lineStarts);
return lineStarts;
}
const commentRegExp = /(['"])(?:(?!\1).|\\\1)*\1|(\/\*.*?\*\/|\/\/.*?$)/gm;
export function removeComments(text: string) {
return text.replace(commentRegExp, (match, quote) => {
return quote ? match : "";
});
}
const testPathPrefixRegExp = /\/\.(test|ts|lib)\//g;
export function removeTestPathPrefixes(text: string) {
return text.replace(testPathPrefixRegExp, "");
}
export function stripBOM(text: string) {
if (text.length >= 2) {
const ch0 = text.charCodeAt(0);
const ch1 = text.charCodeAt(1);
if ((ch0 === 0xff && ch1 === 0xfe) ||
(ch0 === 0xfe && ch1 === 0xff)) {
return text.slice(2);
}
if (text.length >= 3 && ch0 === 0xef && ch1 === 0xbb && text.charCodeAt(2) === 0xbf) {
return text.slice(3);
}
}
return text;
}
//
// Function Utilities
//
export function identity<T>(value: T) {
return value;
}
//
// Array Utilities
//
export function insertAt<T>(array: T[], index: number, value: T) {
if (index === 0) {
array.unshift(value);
}
else if (index === array.length) {
array.push(value);
}
else {
for (let i = array.length; i > index; i--) {
array[i] = array[i - 1];
}
array[index] = value;
}
}
export function removeAt<T>(array: T[], index: number): T | undefined {
const value = index < array.length ? array[index] : undefined;
for (let i = index; i < array.length - 1; i++) {
array[i] = array[i + 1];
}
array.length--;
return value;
}
export function stableSort<T>(array: T[], comparer: (x: T, y: T) => number = compareValues) {
return array
.map((_, i) => i) // create array of indices
.sort((x, y) => comparer(array[x], array[y]) || compareValues(x, y)) // sort indices by value then position
.map(i => array[i]); // get sorted array
}
export function binarySearch<T>(array: ReadonlyArray<T>, value: T, comparer?: (a: T, b: T) => number, offset?: number): number {
return keyedBinarySearch(array, value, identity, comparer, offset);
}
export function keyedBinarySearch<T, K>(array: ReadonlyArray<T>, key: K, keySelector: (value: T) => K, keyComparer: (v1: K, v2: K) => number = compareValues, offset?: number): number {
if (array.length === 0) {
return -1;
}
let low = offset || 0;
let high = array.length - 1;
while (low <= high) {
const middle = low + ((high - low) >> 1);
const midKey = keySelector(array[middle]);
if (keyComparer(midKey, key) === 0) {
return middle;
}
else if (keyComparer(midKey, key) > 0) {
high = middle - 1;
}
else {
low = middle + 1;
}
}
return ~low;
}
//
// RegExp Utilities
//
const reservedCharacterRegExp = /[^\w\s\/]/g;
export function escapeRegExp(pattern: string) {
return pattern.replace(reservedCharacterRegExp, match => "\\" + match);
}
//
// Path Utilities
//
export function isTypeScriptFile(path: string) {
return path.endsWith(".ts")
|| path.endsWith(".tsx");
}
export function isJavaScriptFile(path: string) {
return path.endsWith(".js")
|| path.endsWith(".jsx");
}
export function isDeclarationFile(path: string) {
return path.endsWith(".d.ts");
}
export function isSourceMapFile(path: string) {
return path.endsWith(".map");
}
export function isJsonFile(path: string) {
return path.endsWith(".json");
}
export function isDefaultLibraryFile(path: string) {
return isDeclarationFile(path)
&& vpath.basename(path).startsWith("lib.");
}
export function isBuiltFile(path: string) {
return path.includes("built/local/")
|| path.includes("built/harness/");
}
+917
View File
@@ -0,0 +1,917 @@
import { EventEmitter } from "events";
import { compareStrings, getBuiltDirectory, getLibFilesDirectory, escapeRegExp, identity } from "./utils";
import { KeyedCollection, Metadata } from "./collections";
import { assert } from "chai";
import * as vpath from "./vpath";
import * as io from "./io";
import { TestCaseOptions } from "./testCaseParser";
import { TextDocument } from "./documents";
export interface PathMappings {
[path: string]: string;
}
export interface FileSystemResolver {
getEntries(dir: VirtualDirectory): { files: string[], directories: string[] };
getContent(file: VirtualFile): string | undefined;
}
function createMapper(ignoreCase: boolean, map: PathMappings | undefined) {
if (!map) return identity;
const roots = Object.keys(map);
const patterns = roots.map(root => createPattern(root, ignoreCase));
return function (path: string) {
for (let i = 0; i < patterns.length; i++) {
const match = patterns[i].exec(path);
if (match) {
const prefix = path.slice(0, match.index);
const suffix = path.slice(match.index + match[0].length);
return vpath.combine(prefix, map[roots[i]], suffix);
}
}
return path;
};
}
function createPattern(path: string, ignoreCase: boolean) {
path = vpath.normalizeSlashes(path);
const components = vpath.parse(path);
let pattern = "";
for (let i = 1; i < components.length; i++) {
const component = components[i];
if (pattern) pattern += "/";
pattern += escapeRegExp(component);
}
pattern = (components[0] ? "^" + escapeRegExp(components[0]) : "/") + pattern + "(/|$)";
return new RegExp(pattern, ignoreCase ? "i" : "");
}
export function createResolver(io: io.IO, map?: PathMappings): FileSystemResolver {
const mapper = createMapper(!io.useCaseSensitiveFileNames(), map);
return {
getEntries(dir) {
return io.getAccessibleFileSystemEntries(mapper(dir.path));
},
getContent(file) {
return io.readFile(mapper(file.path));
}
};
}
/**
* Represents a file system entry in a virtual file system.
*/
export abstract class VirtualFileSystemEntry extends EventEmitter {
private _readOnly = false;
private _path: string | undefined;
private _metadata: Metadata | undefined;
/**
* Gets the file system to which this entry belongs.
*/
public readonly fileSystem: VirtualFileSystem;
/**
* Gets the container for this file system entry.
*/
public readonly parent: VirtualFileSystemContainer;
/**
* Gets the name of this file system entry.
*/
public readonly name: string;
constructor(parent: VirtualFileSystemContainer | undefined, name: string) {
super();
if (this instanceof VirtualFileSystem) {
this.parent = this.fileSystem = this;
}
else if (parent instanceof VirtualDirectoryRoot) {
this.parent = this.fileSystem = parent.fileSystem;
}
else if (parent) {
this.parent = parent;
this.fileSystem = parent.fileSystem;
}
else {
throw new TypeError("Argument not optional: parent");
}
this.name = name;
}
/**
* Gets the file system entry that this entry shadows.
*/
public abstract get shadowRoot(): VirtualFileSystemEntry | undefined;
public get metadata(): Metadata {
if (!this._metadata) {
this._metadata = new Metadata(this.shadowRoot ? this.shadowRoot.metadata : undefined);
}
return this._metadata;
}
public get isReadOnly(): boolean {
return this._readOnly;
}
public get path(): string {
return this._path || (this._path = vpath.combine(this.parent.path, this.name));
}
public get relative(): string {
return this.relativeTo(this.fileSystem.currentDirectory);
}
public get exists(): boolean {
return this.parent.exists
&& this.parent.getEntry(this.name) as VirtualFileSystemEntry === this;
}
public makeReadOnly(): void {
this.makeReadOnlyCore();
this._readOnly = true;
}
public relativeTo(other: string | VirtualFileSystemEntry) {
if (other) {
const otherPath = typeof other === "string" ? other : other.path;
const ignoreCase = !this.fileSystem.useCaseSensitiveFileNames;
return vpath.relative(otherPath, this.path, ignoreCase);
}
return this.path;
}
/**
* Creates a file system entry that shadows this file system entry.
* @param parent The container for the shadowed entry.
*/
public abstract shadow(parent: VirtualFileSystemContainer): VirtualFileSystemEntry;
protected abstract makeReadOnlyCore(): void;
protected writePreamble(): void {
if (this._readOnly) throw new Error("Cannot modify a frozen entry.");
}
protected shadowPreamble(parent: VirtualFileSystemContainer): void {
let fileSystem: VirtualFileSystem | undefined = this.fileSystem;
while (fileSystem) {
if (parent.fileSystem === fileSystem) throw new Error("Cannot create shadow for parent in the same file system.");
fileSystem = fileSystem.shadowRoot;
}
}
}
export abstract class VirtualFileSystemContainer extends VirtualFileSystemEntry {
public abstract get shadowRoot(): VirtualFileSystemContainer | undefined;
public getEntries(options: { recursive?: boolean, pattern?: RegExp, kind: "file" }): VirtualFile[];
public getEntries(options: { recursive?: boolean, pattern?: RegExp, kind: "directory" }): VirtualDirectory[];
public getEntries(options?: { recursive?: boolean, pattern?: RegExp, kind?: "file" | "directory" }): (VirtualFile | VirtualDirectory)[];
public getEntries(options: { recursive?: boolean, pattern?: RegExp, kind?: "file" | "directory" } = {}): (VirtualFile | VirtualDirectory)[] {
const results: (VirtualFile | VirtualDirectory)[] = [];
if (options.recursive) {
this.getOwnEntries().forEach(entry => {
if (entry instanceof VirtualFile) {
if (isMatch(entry, options)) {
results.push(entry);
}
}
else if (entry instanceof VirtualDirectory) {
if (isMatch(entry, options)) {
results.push(entry);
}
for (const child of entry.getEntries(options)) {
results.push(child);
}
}
});
}
else {
this.getOwnEntries().forEach(entry => {
if (isMatch(entry, options)) {
results.push(entry);
}
});
}
return results;
}
public getDirectories(options: { recursive?: boolean, pattern?: RegExp } = {}): VirtualDirectory[] {
return this.getEntries({ kind: "directory", ...options });
}
public getFiles(options: { recursive?: boolean, pattern?: RegExp } = {}): VirtualFile[] {
return this.getEntries({ kind: "file", ...options });
}
public getEntryNames(options: { recursive?: boolean, qualified?: boolean, pattern?: RegExp, kind?: "file" | "directory" } = {}): string[] {
return this.getEntries(options).map(entry =>
options && options.qualified ? entry.path :
options && options.recursive ? entry.relativeTo(this) :
entry.name);
}
public getDirectoryNames(options: { recursive?: boolean, qualified?: boolean, pattern?: RegExp } = {}): string[] {
return this.getEntryNames({ kind: "directory", ...options });
}
public getFileNames(options: { recursive?: boolean, qualified?: boolean, pattern?: RegExp } = {}): string[] {
return this.getEntryNames({ kind: "file", ...options });
}
public abstract getEntry(path: string, options: { followSymlinks?: boolean, pattern?: RegExp, kind: "file" }): VirtualFile | undefined;
public abstract getEntry(path: string, options: { followSymlinks?: boolean, pattern?: RegExp, kind: "directory" }): VirtualDirectory | undefined;
public abstract getEntry(path: string, options?: { followSymlinks?: boolean, pattern?: RegExp, kind?: "file" | "directory" }): VirtualFile | VirtualDirectory | undefined;
public getDirectory(path: string, options: { followSymlinks?: boolean, pattern?: RegExp } = {}): VirtualDirectory | undefined {
return this.getEntry(path, { kind: "directory", ...options });
}
public getFile(path: string, options: { followSymlinks?: boolean, pattern?: RegExp } = {}): VirtualFile | undefined {
return this.getEntry(path, { kind: "file", ...options });
}
protected abstract getOwnEntries(): KeyedCollection<string, VirtualFile | VirtualDirectory>;
}
export interface VirtualFileSystemContainer {
on(name: "childAdded", handler: (entry: VirtualFile | VirtualDirectory) => void): this;
on(name: "childRemoved", handler: (entry: VirtualFile | VirtualDirectory) => void): this;
}
export class VirtualFileSystem extends VirtualFileSystemContainer {
private static _builtLocal: VirtualFileSystem | undefined;
private static _builtLocalCI: VirtualFileSystem | undefined;
private static _builtLocalCS: VirtualFileSystem | undefined;
private _root: VirtualDirectoryRoot;
private _useCaseSensitiveFileNames: boolean;
private _currentDirectory: string;
private _shadowRoot: VirtualFileSystem | undefined;
constructor(currentDirectory: string, useCaseSensitiveFileNames: boolean) {
super(/*parent*/ undefined, "");
this._currentDirectory = currentDirectory.replace(/\\/g, "/");
this._useCaseSensitiveFileNames = useCaseSensitiveFileNames;
}
public get shadowRoot(): VirtualFileSystem | undefined {
return this._shadowRoot;
}
public get useCaseSensitiveFileNames() {
return this._useCaseSensitiveFileNames;
}
public get currentDirectory() {
return this._currentDirectory;
}
public get path() {
return "";
}
public get relative() {
return "";
}
public get exists() {
return true;
}
private get root() {
if (this._root === undefined) {
if (this._shadowRoot) {
this._root = this._shadowRoot.root.shadow(this);
}
else {
this._root = new VirtualDirectoryRoot(this);
}
if (this.isReadOnly) this._root.makeReadOnly();
}
return this._root;
}
public static getBuiltLocal(useCaseSensitiveFileNames: boolean = io.useCaseSensitiveFileNames()): VirtualFileSystem {
let vfs = useCaseSensitiveFileNames ? this._builtLocalCS : this._builtLocalCI;
if (!vfs) {
vfs = this._builtLocal;
if (!vfs) {
const resolver = createResolver(io, {
"/.ts": getBuiltDirectory(),
"/.lib": getLibFilesDirectory()
});
vfs = new VirtualFileSystem("/", io.useCaseSensitiveFileNames());
vfs.addDirectory(".ts", resolver);
vfs.addDirectory(".lib", resolver);
vfs.makeReadOnly();
this._builtLocal = vfs;
}
if (vfs._useCaseSensitiveFileNames !== useCaseSensitiveFileNames) {
vfs = vfs.shadow();
vfs._useCaseSensitiveFileNames = useCaseSensitiveFileNames;
vfs.makeReadOnly();
}
return useCaseSensitiveFileNames
? this._builtLocalCS = vfs
: this._builtLocalCI = vfs;
}
return vfs;
}
public static createFromOptions(compilerOptions: TestCaseOptions) {
const vfs = this.getBuiltLocal(compilerOptions.useCaseSensitiveFileNames).shadow();
vfs.addDirectory("/.test");
if (compilerOptions.currentDirectory) {
const currentDirectory = vpath.resolve("/.test", compilerOptions.currentDirectory);
vfs.addDirectory(currentDirectory);
vfs.changeDirectory(currentDirectory);
}
else {
vfs.changeDirectory("/.test");
}
return vfs;
}
public static createFromDocuments(compilerOptions: TestCaseOptions, documents: TextDocument[], options?: { overwrite?: boolean }) {
const vfs = this.createFromOptions(compilerOptions);
for (const document of documents) {
const file = vfs.addFile(document.file, document.text, options)!;
assert.isDefined(file, `Failed to add file: '${document.file}'`);
file.metadata.set("document", document);
// Add symlinks
const symlink = document.meta.get("symlink");
if (file && symlink) {
for (const link of symlink.split(",")) {
const symlink = vfs.addSymlink(vpath.resolve(vfs.currentDirectory, link.trim()), file)!;
assert.isDefined(symlink, `Failed to symlink: '${link}'`);
symlink.metadata.set("document", document);
}
}
}
return vfs;
}
public changeDirectory(path: string) {
this.writePreamble();
if (path) {
this._currentDirectory = vpath.resolve(this._currentDirectory, path);
}
}
public addDirectory(path: string, resolver?: FileSystemResolver) {
return this.root.addDirectory(vpath.resolve(this.currentDirectory, path), resolver);
}
public addFile(path: string, content?: FileSystemResolver["getContent"] | string, options?: { overwrite?: boolean }) {
return this.root.addFile(vpath.resolve(this.currentDirectory, path), content, options);
}
public addSymlink(path: string, target: VirtualFile): VirtualFileSymlink | undefined;
public addSymlink(path: string, target: VirtualDirectory): VirtualDirectorySymlink | undefined;
public addSymlink(path: string, target: string | VirtualFile | VirtualDirectory): VirtualSymlink | undefined;
public addSymlink(path: string, target: string | VirtualFile | VirtualDirectory) {
if (typeof target === "string") target = vpath.resolve(this.currentDirectory, target);
return this.root.addSymlink(vpath.resolve(this.currentDirectory, path), target);
}
public removeDirectory(path: string): boolean {
return this.root.removeDirectory(vpath.resolve(this.currentDirectory, path));
}
public removeFile(path: string): boolean {
return this.root.removeFile(vpath.resolve(this.currentDirectory, path));
}
public directoryExists(path: string) {
return this.getEntry(path) instanceof VirtualDirectory;
}
public fileExists(path: string) {
return this.getEntry(path) instanceof VirtualFile;
}
public sameName(a: string, b: string) {
return compareStrings(a, b, !this.useCaseSensitiveFileNames) === 0;
}
public getRealEntry(entry: VirtualDirectory): VirtualDirectory | undefined;
public getRealEntry(entry: VirtualFile): VirtualFile | undefined;
public getRealEntry(entry: VirtualFile | VirtualDirectory): VirtualFile | VirtualDirectory | undefined;
public getRealEntry(entry: VirtualFile | VirtualDirectory): VirtualFile | VirtualDirectory | undefined {
if (entry instanceof VirtualFileSymlink || entry instanceof VirtualDirectorySymlink) {
return findTarget(this, entry.target);
}
return entry;
}
public getEntry(path: string, options: { followSymlinks?: boolean, pattern?: RegExp, kind: "file" }): VirtualFile | undefined;
public getEntry(path: string, options: { followSymlinks?: boolean, pattern?: RegExp, kind: "directory" }): VirtualDirectory | undefined;
public getEntry(path: string, options?: { followSymlinks?: boolean, pattern?: RegExp, kind?: "file" | "directory" }): VirtualFile | VirtualDirectory | undefined;
public getEntry(path: string, options?: { followSymlinks?: boolean, pattern?: RegExp, kind?: "file" | "directory" }) {
return this.root.getEntry(vpath.resolve(this.currentDirectory, path), options);
}
public getFile(path: string, options?: { followSymlinks?: boolean, pattern?: RegExp }): VirtualFile | undefined {
return this.root.getFile(vpath.resolve(this.currentDirectory, path), options);
}
public getDirectory(path: string, options?: { followSymlinks?: boolean, pattern?: RegExp }): VirtualDirectory | undefined {
return this.root.getDirectory(vpath.resolve(this.currentDirectory, path), options);
}
public getAccessibleFileSystemEntries(path: string) {
const entry = this.getEntry(path);
if (entry instanceof VirtualDirectory) {
return {
files: entry.getFiles().map(f => f.name),
directories: entry.getDirectories().map(d => d.name)
};
}
return { files: [], directories: [] };
}
public shadow(): VirtualFileSystem {
const fs = new VirtualFileSystem(this.currentDirectory, this.useCaseSensitiveFileNames);
fs._shadowRoot = this;
return fs;
}
protected makeReadOnlyCore() {
this.root.makeReadOnly();
}
protected getOwnEntries() {
return this.root["getOwnEntries"]();
}
}
export class VirtualDirectory extends VirtualFileSystemContainer {
protected _shadowRoot: VirtualDirectory | undefined;
private _entries: KeyedCollection<string, VirtualFile | VirtualDirectory> | undefined;
private _resolver: FileSystemResolver | undefined;
constructor(parent: VirtualFileSystemContainer, name: string, resolver?: FileSystemResolver) {
super(parent, name);
this._entries = undefined;
this._resolver = resolver;
this._shadowRoot = undefined;
}
public get shadowRoot(): VirtualDirectory | undefined {
return this._shadowRoot;
}
public getEntry(path: string, options: { followSymlinks?: boolean, pattern?: RegExp, kind: "file" }): VirtualFile | undefined;
public getEntry(path: string, options: { followSymlinks?: boolean, pattern?: RegExp, kind: "directory" }): VirtualDirectory | undefined;
public getEntry(path: string, options?: { followSymlinks?: boolean, pattern?: RegExp, kind?: "file" | "directory" }): VirtualFile | VirtualDirectory | undefined;
public getEntry(path: string, options: { followSymlinks?: boolean, pattern?: RegExp, kind?: "file" | "directory" } = {}): VirtualFile | VirtualDirectory | undefined {
const components = this.parsePath(path);
const directory = this.walkContainers(components, /*create*/ false);
return directory && directory.getOwnEntry(components[components.length - 1], options);
}
public addDirectory(path: string, resolver?: FileSystemResolver): VirtualDirectory | undefined {
this.writePreamble();
const components = this.parsePath(path);
const directory = this.walkContainers(components, /*create*/ true);
return directory && directory.addOwnDirectory(components[components.length - 1], resolver);
}
public addFile(path: string, content?: FileSystemResolver["getContent"] | string | undefined, options?: { overwrite?: boolean }): VirtualFile | undefined {
this.writePreamble();
const components = this.parsePath(path);
const directory = this.walkContainers(components, /*create*/ true);
return directory && directory.addOwnFile(components[components.length - 1], content, options);
}
public addSymlink(path: string, target: VirtualFile): VirtualFileSymlink | undefined;
public addSymlink(path: string, target: VirtualDirectory): VirtualDirectorySymlink | undefined;
public addSymlink(path: string, target: string | VirtualFile | VirtualDirectory): VirtualSymlink | undefined;
public addSymlink(path: string, target: string | VirtualFile | VirtualDirectory): VirtualSymlink | undefined {
this.writePreamble();
const targetEntry = typeof target === "string" ? this.fileSystem.getEntry(vpath.resolve(this.path, target)) : target;
if (targetEntry === undefined) return undefined;
const components = this.parsePath(path);
const directory = this.walkContainers(components, /*create*/ true);
return directory && directory.addOwnSymlink(components[components.length - 1], targetEntry);
}
public removeDirectory(path: string): boolean {
this.writePreamble();
const components = this.parsePath(path);
const directory = this.walkContainers(components, /*create*/ false);
return directory ? directory.removeOwnDirectory(components[components.length - 1]) : false;
}
public removeFile(path: string): boolean {
this.writePreamble();
this.writePreamble();
const components = this.parsePath(path);
const directory = this.walkContainers(components, /*create*/ false);
return directory ? directory.removeOwnFile(components[components.length - 1]) : false;
}
public shadow(parent: VirtualFileSystemContainer): VirtualDirectory {
this.shadowPreamble(parent);
const shadow = new VirtualDirectory(parent, this.name);
shadow._shadowRoot = this;
return shadow;
}
protected makeReadOnlyCore(): void {
if (this._entries) {
this._entries.forEach(entry => entry.makeReadOnly());
}
}
protected getOwnEntries() {
if (!this._entries) {
const resolver = this._resolver;
const entries = new KeyedCollection<string, VirtualFile | VirtualDirectory>(this.fileSystem.useCaseSensitiveFileNames ? compareStrings.caseSensitive : compareStrings.caseInsensitive);
this._resolver = undefined;
if (resolver) {
const { files, directories } = resolver.getEntries(this);
for (const dir of directories) {
const vdir = new VirtualDirectory(this, dir, resolver);
if (this.isReadOnly) vdir.makeReadOnly();
entries.set(vdir.name, vdir);
}
for (const file of files) {
const vfile = new VirtualFile(this, file, file => resolver.getContent(file));
if (this.isReadOnly) vfile.makeReadOnly();
entries.set(vfile.name, vfile);
}
}
else if (this._shadowRoot) {
this._shadowRoot.getOwnEntries().forEach(entry => {
const clone = <VirtualFile | VirtualDirectory>(<VirtualFileSystemEntry>entry).shadow(this);
if (this.isReadOnly) clone.makeReadOnly();
entries.set(clone.name, clone);
});
}
this._entries = entries;
}
return this._entries;
}
private parsePath(path: string) {
if (this instanceof VirtualDirectoryRoot) path = vpath.resolve(this.fileSystem.currentDirectory, path);
return vpath.parse(vpath.normalize(path));
}
private walkContainers(components: string[], create: boolean) {
// no absolute paths (unless this is the root)
if (!!components[0] === !(this instanceof VirtualDirectoryRoot)) return undefined;
// no relative paths
if (components[1] === "..") return undefined;
// walk the components
let directory: VirtualDirectory | undefined = this;
for (let i = this instanceof VirtualDirectoryRoot ? 0 : 1; i < components.length - 1; i++) {
directory = create ? directory.getOrAddOwnDirectory(components[i]) : directory.getOwnDirectory(components[i]);
if (directory === undefined) return undefined;
}
return directory;
}
private getOwnEntry(name: string, options: { followSymlinks?: boolean, pattern?: RegExp, kind: "file" }): VirtualFile | undefined;
private getOwnEntry(name: string, options: { followSymlinks?: boolean, pattern?: RegExp, kind: "directory" }): VirtualDirectory | undefined;
private getOwnEntry(name: string, options?: { followSymlinks?: boolean, pattern?: RegExp, kind?: "file" | "directory" }): VirtualFile | VirtualDirectory | undefined;
private getOwnEntry(name: string, options: { followSymlinks?: boolean, pattern?: RegExp, kind?: "file" | "directory" } = {}): VirtualFile | VirtualDirectory | undefined {
const entry = this.getOwnEntries().get(name);
return entry && isMatch(entry, options) ? options.followSymlinks ? this.fileSystem.getRealEntry(entry) : entry : undefined;
}
private getOwnDirectory(name: string) {
return this.getOwnEntry(name, { kind: "directory" });
}
private getOrAddOwnDirectory(name: string) {
return this.getOwnDirectory(name) || this.addOwnDirectory(name);
}
private addOwnDirectory(name: string, resolver?: FileSystemResolver): VirtualDirectory | undefined {
const existing = this.getOwnEntry(name);
if (existing) {
if (!resolver && existing instanceof VirtualDirectory) {
return existing;
}
return undefined;
}
const entry = new VirtualDirectory(this, name, resolver);
this.getOwnEntries().set(entry.name, entry);
this.emit("childAdded", entry);
return entry;
}
private addOwnFile(name: string, content?: FileSystemResolver["getContent"] | string | undefined, options: { overwrite?: boolean } = {}): VirtualFile | undefined {
const existing = this.getOwnEntry(name);
if (existing) {
if (!options.overwrite || !(existing instanceof VirtualFile)) {
return undefined;
}
// Remove the existing entry
this.getOwnEntries().delete(name);
}
const entry = new VirtualFile(this, name, content);
this.getOwnEntries().set(entry.name, entry);
this.emit("childAdded", entry);
return entry;
}
private addOwnSymlink(name: string, target: VirtualFile | VirtualDirectory): VirtualSymlink | undefined {
if (this.getOwnEntry(name)) return undefined;
const entry = target instanceof VirtualFile ? new VirtualFileSymlink(this, name, target.path) : new VirtualDirectorySymlink(this, name, target.path);
this.getOwnEntries().set(entry.name, entry);
this.emit("childAdded", entry);
return entry;
}
private removeOwnDirectory(name: string) {
const entries = this.getOwnEntries();
return entries.get(name) instanceof VirtualDirectory ? entries.delete(name) : false;
}
private removeOwnFile(name: string) {
const entries = this.getOwnEntries();
return entries.get(name) instanceof VirtualFile ? entries.delete(name) : false;
}
}
class VirtualDirectoryRoot extends VirtualDirectory {
constructor(parent: VirtualFileSystem) {
super(parent, "");
}
public shadow(parent: VirtualFileSystem): VirtualDirectory {
this.shadowPreamble(parent);
const shadow = new VirtualDirectoryRoot(parent);
shadow._shadowRoot = this;
return shadow;
}
}
export class VirtualDirectorySymlink extends VirtualDirectory {
private _targetPath: string;
private _target: VirtualDirectory | undefined;
private _symLinks = new Map<VirtualFile | VirtualDirectory, VirtualSymlink>();
private _symEntries: KeyedCollection<string, VirtualSymlink> | undefined;
private _onTargetParentChildRemoved: (entry: VirtualFile | VirtualDirectory) => void;
private _onTargetChildRemoved: (entry: VirtualFile | VirtualDirectory) => void;
private _onTargetChildAdded: (entry: VirtualFile | VirtualDirectory) => void;
constructor(parent: VirtualFileSystemContainer, name: string, target: string) {
super(parent, name);
this._targetPath = target;
this._onTargetParentChildRemoved = entry => this.onTargetParentChildRemoved(entry);
this._onTargetChildAdded = entry => this.onTargetChildAdded(entry);
this._onTargetChildRemoved = entry => this.onTargetChildRemoved(entry);
}
public get target() {
return this._targetPath;
}
public set target(value: string) {
this.writePreamble();
if (this._targetPath !== value) {
this._targetPath = value;
this.invalidateTarget();
}
}
public get isBroken(): boolean {
return this.getRealDirectory() === undefined;
}
public getRealDirectory(): VirtualDirectory | undefined {
this.resolveTarget();
return this._target;
}
public addDirectory(path: string, resolver?: FileSystemResolver): VirtualDirectory | undefined {
const target = this.getRealDirectory();
return target && target.addDirectory(path, resolver);
}
public addFile(path: string, content?: FileSystemResolver["getContent"] | string | undefined): VirtualFile | undefined {
const target = this.getRealDirectory();
return target && target.addFile(path, content);
}
public removeDirectory(path: string): boolean {
const target = this.getRealDirectory();
return target && target.removeDirectory(path) || false;
}
public removeFile(path: string): boolean {
const target = this.getRealDirectory();
return target && target.removeFile(path) || false;
}
public shadow(parent: VirtualFileSystemContainer): VirtualDirectorySymlink {
this.shadowPreamble(parent);
const shadow = new VirtualDirectorySymlink(parent, this.name, this.target);
shadow._shadowRoot = this;
return shadow;
}
public resolveTarget(): void {
if (!this._target) {
const entry = findTarget(this.fileSystem, this.target);
if (entry instanceof VirtualDirectory) {
this._target = entry;
this._target.parent.on("childRemoved", this._onTargetParentChildRemoved);
this._target.on("childAdded", this._onTargetChildAdded);
this._target.on("childRemoved", this._onTargetChildRemoved);
}
}
}
protected getOwnEntries(): KeyedCollection<string, VirtualSymlink> {
if (!this._symEntries) {
const target = this.getRealDirectory();
this._symEntries = new KeyedCollection<string, VirtualSymlink>(this.fileSystem.useCaseSensitiveFileNames ? compareStrings.caseSensitive : compareStrings.caseInsensitive);
if (target) {
for (const entry of target.getEntries()) {
this._symEntries.set(entry.name, this.getWrappedEntry(entry));
}
}
}
return this._symEntries;
}
private getWrappedEntry(entry: VirtualFile | VirtualDirectory) {
let symlink = this._symLinks.get(entry);
if (entry instanceof VirtualFile) {
if (symlink instanceof VirtualFileSymlink) {
return symlink;
}
symlink = new VirtualFileSymlink(this, entry.name, entry.path);
this._symLinks.set(entry, symlink);
}
else {
if (symlink instanceof VirtualDirectorySymlink) {
return symlink;
}
symlink = new VirtualDirectorySymlink(this, entry.name, entry.path);
this._symLinks.set(entry, symlink);
}
return symlink;
}
private onTargetParentChildRemoved(entry: VirtualFileSystemEntry) {
if (entry !== this._target) return;
this.invalidateTarget();
}
private onTargetChildAdded(entry: VirtualFile | VirtualDirectory) {
const wrapped = this.getWrappedEntry(entry);
this.getOwnEntries().set(entry.name, wrapped);
this.emit("childAdded", wrapped);
}
private onTargetChildRemoved(entry: VirtualFile | VirtualDirectory) {
const wrapped = this.getWrappedEntry(entry);
this.getOwnEntries().delete(entry.name);
this._symLinks.delete(entry);
this.emit("childRemoved", wrapped);
}
private invalidateTarget() {
if (!this._target) return;
this._target.parent.removeListener("childRemoved", this._onTargetParentChildRemoved);
this._target.removeListener("childAdded", this._onTargetChildAdded);
this._target.removeListener("childRemoved", this._onTargetChildRemoved);
this._target = undefined;
this._symLinks.clear();
this._symEntries = undefined;
}
}
export class VirtualFile extends VirtualFileSystemEntry {
protected _shadowRoot: VirtualFile | undefined;
private _content: string | undefined;
private _contentWasSet: boolean;
private _resolver: FileSystemResolver["getContent"] | undefined;
constructor(parent: VirtualDirectory, name: string, content?: FileSystemResolver["getContent"] | string | undefined) {
super(parent, name);
this._content = typeof content === "string" ? content : undefined;
this._resolver = typeof content === "function" ? content : undefined;
this._shadowRoot = undefined;
this._contentWasSet = this._content !== undefined;
}
public get shadowRoot(): VirtualFile | undefined {
return this._shadowRoot;
}
public getContent(): string | undefined {
if (!this._contentWasSet) {
const resolver = this._resolver;
const shadowRoot = this._shadowRoot;
if (resolver) {
this._content = resolver(this);
this._contentWasSet = true;
}
else if (shadowRoot) {
this._content = shadowRoot.getContent();
this._contentWasSet = true;
}
}
return this._content;
}
public setContent(value: string | undefined) {
this.writePreamble();
this._resolver = undefined;
this._content = value;
this._contentWasSet = true;
}
public shadow(parent: VirtualDirectory): VirtualFile {
this.shadowPreamble(parent);
const shadow = new VirtualFile(parent, this.name);
shadow._shadowRoot = this;
shadow._contentWasSet = false;
return shadow;
}
protected makeReadOnlyCore(): void {
}
}
export class VirtualFileSymlink extends VirtualFile {
private _target: string;
constructor(parent: VirtualDirectory, name: string, target: string) {
super(parent, name);
this._target = target;
}
public get target(): string {
return this._target;
}
public set target(value: string) {
this.writePreamble();
this._target = value;
}
public get isBroken(): boolean {
return this.getRealFile() === undefined;
}
public getRealFile(): VirtualFile | undefined {
const entry = findTarget(this.fileSystem, this.target);
return entry instanceof VirtualFile ? entry : undefined;
}
public getContent(): string | undefined {
const target = this.getRealFile();
return target && target.getContent();
}
public setContent(value: string | undefined) {
const target = this.getRealFile();
if (target) target.setContent(value);
}
public shadow(parent: VirtualDirectory) {
this.shadowPreamble(parent);
const shadow = new VirtualFileSymlink(parent, this.name, this.target);
shadow._shadowRoot = this;
return shadow;
}
}
export type VirtualSymlink = VirtualDirectorySymlink | VirtualFileSymlink;
function findTarget(vfs: VirtualFileSystem, target: string, set?: Set<VirtualFileSymlink | VirtualDirectorySymlink>): VirtualFile | VirtualDirectory | undefined {
const entry = vfs.getEntry(target);
if (entry instanceof VirtualFileSymlink || entry instanceof VirtualDirectorySymlink) {
if (!set) set = new Set<VirtualFileSymlink | VirtualDirectorySymlink>();
if (set.has(entry)) return undefined;
set.add(entry);
return findTarget(vfs, entry.target, set);
}
return entry;
}
function isMatch(entry: VirtualFile | VirtualDirectory, options: { pattern?: RegExp, kind?: "file" | "directory" }) {
return (options.pattern === undefined || options.pattern.test(entry.name))
&& (options.kind !== (entry instanceof VirtualFile ? "directory" : "file"));
}
+155
View File
@@ -0,0 +1,155 @@
import { compareStrings } from "./utils";
export function normalizeSlashes(path: string): string {
return path.replace(/\s*[\\/]\s*/g, "/").trim();
}
const rootRegExp = /^[\\/]([\\/](.*?[\\/](.*?[\\/])?)?)?|^[a-zA-Z]:[\\/]?|^\w+:\/{2}[^\\/]*\/?/;
function getRootLength(path: string) {
const match = rootRegExp.exec(path);
return match ? match[0].length : 0;
}
export function isAbsolute(path: string) {
return rootRegExp.test(path);
}
const trailingSeperatorRegExp = /[\\/]$/;
export function hasTrailingSeperator(path: string) {
return trailingSeperatorRegExp.test(path);
}
function reduce(components: string[]) {
const normalized = [components[0]];
for (let i = 1; i < components.length; i++) {
const component = components[i];
if (component === ".") continue;
if (component === ".." && normalized.length > 0 && normalized[normalized.length - 1] !== "..") {
normalized.pop();
}
else {
normalized.push(component);
}
}
return normalized;
}
export function normalize(path: string): string {
const components = reduce(parse(path));
return components.length > 1 && hasTrailingSeperator(path) ? format(components) + "/" : format(components);
}
export function combine(path: string, ...paths: string[]) {
path = normalizeSlashes(path);
for (let name of paths) {
name = normalizeSlashes(name);
if (name.length === 0) continue;
if (path.length === 0 || isAbsolute(name)) {
path = name;
}
else {
path = hasTrailingSeperator(path) ? path + name : path + "/" + name;
}
}
return path;
}
export function resolve(path: string, ...paths: string[]) {
return normalize(combine(path, ...paths));
}
export function relative(from: string, to: string, ignoreCase: boolean) {
if (!isAbsolute(from)) throw new Error("Path not absolute");
if (!isAbsolute(to)) throw new Error("Path not absolute");
const fromComponents = reduce(parse(from));
const toComponents = reduce(parse(to));
let start: number;
for (start = 0; start < fromComponents.length && start < toComponents.length; start++) {
if (compareStrings(fromComponents[start], toComponents[start], ignoreCase)) {
break;
}
}
if (start === 0) {
return format(toComponents);
}
const components = toComponents.slice(start);
for (; start < fromComponents.length; start++) {
components.unshift("..");
}
return format(["", ...components]);
}
export function beneath(ancestor: string, descendant: string, ignoreCase: boolean) {
if (!isAbsolute(ancestor)) throw new Error("Path not absolute");
if (!isAbsolute(descendant)) throw new Error("Path not absolute");
const ancestorComponents = reduce(parse(ancestor));
const descendantComponents = reduce(parse(descendant));
let start: number;
for (start = 0; start < ancestorComponents.length && start < descendantComponents.length; start++) {
if (compareStrings(ancestorComponents[start], descendantComponents[start], ignoreCase)) {
break;
}
}
return start === ancestorComponents.length;
}
export function parse(path: string) {
path = normalizeSlashes(path);
const rootLength = getRootLength(path);
const root = path.substring(0, rootLength);
const rest = path.substring(rootLength).split(/\/+/g);
if (rest.length && !rest[rest.length - 1]) rest.pop();
return [root, ...rest.map(component => component.trim())];
}
export function format(components: string[]) {
return components.length ? components[0] + components.slice(1).join("/") : "";
}
export function dirname(path: string) {
path = normalizeSlashes(path);
return path.substr(0, Math.max(getRootLength(path), path.lastIndexOf("/")));
}
export function basename(path: string, ext?: string): string;
export function basename(path: string, options?: { extensions?: string[], ignoreCase?: boolean }): string;
export function basename(path: string, options?: { extensions?: string[], ignoreCase?: boolean } | string) {
path = normalizeSlashes(path);
const name = path.substr(Math.max(getRootLength(path), path.lastIndexOf("/") + 1));
const extension = typeof options === "string" ? options.startsWith(".") ? options : "." + options :
options && options.extensions ? extname(name, options) :
undefined;
return extension ? name.slice(0, name.length - extension.length) : name;
}
const extRegExp = /\.\w+$/;
export function extname(path: string, options?: { extensions?: string[], ignoreCase?: boolean }) {
if (options && options.extensions) {
for (let extension of options.extensions) {
if (!extension.startsWith(".")) extension = "." + extension;
if (path.length > extension.length) {
const ext = path.slice(path.length - extension.length);
if (compareStrings(ext, extension, options.ignoreCase) === 0) {
return ext;
}
}
}
return "";
}
const match = extRegExp.exec(path);
return match ? match[0] : "";
}
export function chext(path: string, ext: string, options?: { extensions?: string[], ignoreCase?: boolean }) {
const pathext = extname(path, options);
return pathext ? path.slice(0, path.length - pathext.length) + (ext.startsWith(".") ? ext : "." + ext) : path;
}
+54
View File
@@ -1346,3 +1346,57 @@ namespace ts {
return getTokenAtPosition(sourceFile, declaration.members.pos - 1, /*includeJsDocComment*/ false);
}
}
// Helpers for tests
/* @internal */
namespace ts {
export interface TypesAndSymbols {
line: number;
text: string;
type: string | undefined;
symbol: string | undefined;
declarations: { fileName: string, line: number, character: number }[] | undefined;
}
/**
* Helper used by the test harness to collect types and symbols in a file.
*/
export function getTypesAndSymbols(program: Program, fileName: string, checked: boolean, exclude: "types" | "symbols" | undefined): TypesAndSymbols[] {
const checker = checked ? program.getDiagnosticsProducingTypeChecker() : program.getTypeChecker();
const sourceFile = program.getSourceFile(fileName);
const results: TypesAndSymbols[] = [];
visitNode(sourceFile);
return results;
function visitNode(node: Node) {
if (node) {
if (isPartOfExpression(node) || isIdentifier(node)) {
writeTypeAndSymbol(node);
}
forEachChild(node, visitNode);
}
}
function writeTypeAndSymbol(node: Node) {
const start = node.getStart();
const { line } = sourceFile.getLineAndCharacterOfPosition(start);
// Workaround to ensure we output 'C' instead of 'typeof C' for base class expressions
const type = exclude !== "types" && node.parent && isExpressionWithTypeArgumentsInClassExtendsClause(node.parent) && checker.getTypeAtLocation(node.parent) || checker.getTypeAtLocation(node);
const symbol = exclude !== "symbols" && checker.getSymbolAtLocation(node);
const declarations = symbol && symbol.declarations && symbol.declarations.map(declaration => {
const file = declaration.getSourceFile();
const { line, character } = file.getLineAndCharacterOfPosition(declaration.pos);
return { fileName: file.fileName, line, character };
});
results.push({
line,
text: node.getText(),
type: type && checker.typeToString(type, node.parent, TypeFormatFlags.NoTruncation),
symbol: symbol && checker.symbolToString(symbol, node.parent),
declarations: declarations
});
}
}
}
@@ -1,4 +1,41 @@
//// [duplicateIdentifierShouldNotShortCircuitBaseTypeBinding_0.ts]
//// [tests/cases/compiler/duplicateIdentifierShouldNotShortCircuitBaseTypeBinding.ts] ////
//// [duplicateIdentifierShouldNotShortCircuitBaseTypeBinding_0.ts]
export interface IPoint {}
export module Shapes {
export class Point implements IPoint {}
}
//// [duplicateIdentifierShouldNotShortCircuitBaseTypeBinding_1.ts]
//var x = new Shapes.Point();
//interface IPoint {}
//module Shapes {
// export class Point implements IPoint {}
//}
//// [duplicateIdentifierShouldNotShortCircuitBaseTypeBinding_0.js]
define(["require", "exports"], function (require, exports) {
"use strict";
exports.__esModule = true;
var Shapes;
(function (Shapes) {
var Point = (function () {
function Point() {
}
return Point;
}());
Shapes.Point = Point;
})(Shapes = exports.Shapes || (exports.Shapes = {}));
});
//// [duplicateIdentifierShouldNotShortCircuitBaseTypeBinding_1.js]
//var x = new Shapes.Point();
//interface IPoint {}
//module Shapes {
// export class Point implements IPoint {}
//}
@@ -1,3 +1,23 @@
=== tests/cases/compiler/duplicateIdentifierShouldNotShortCircuitBaseTypeBinding_0.ts ===
export interface IPoint {}
>IPoint : Symbol(IPoint, Decl(duplicateIdentifierShouldNotShortCircuitBaseTypeBinding_0.ts, 0, 0))
export module Shapes {
>Shapes : Symbol(Shapes, Decl(duplicateIdentifierShouldNotShortCircuitBaseTypeBinding_0.ts, 0, 26))
export class Point implements IPoint {}
>Point : Symbol(Point, Decl(duplicateIdentifierShouldNotShortCircuitBaseTypeBinding_0.ts, 2, 22))
>IPoint : Symbol(IPoint, Decl(duplicateIdentifierShouldNotShortCircuitBaseTypeBinding_0.ts, 0, 0))
}
=== tests/cases/compiler/duplicateIdentifierShouldNotShortCircuitBaseTypeBinding_1.ts ===
//var x = new Shapes.Point();
No type information for this code.//interface IPoint {}
No type information for this code.
No type information for this code.//module Shapes {
No type information for this code.
No type information for this code.// export class Point implements IPoint {}
No type information for this code.
No type information for this code.//}
No type information for this code.
@@ -1,3 +1,23 @@
=== tests/cases/compiler/duplicateIdentifierShouldNotShortCircuitBaseTypeBinding_0.ts ===
export interface IPoint {}
>IPoint : IPoint
export module Shapes {
>Shapes : typeof Shapes
export class Point implements IPoint {}
>Point : Point
>IPoint : IPoint
}
=== tests/cases/compiler/duplicateIdentifierShouldNotShortCircuitBaseTypeBinding_1.ts ===
//var x = new Shapes.Point();
No type information for this code.//interface IPoint {}
No type information for this code.
No type information for this code.//module Shapes {
No type information for this code.
No type information for this code.// export class Point implements IPoint {}
No type information for this code.
No type information for this code.//}
No type information for this code.
@@ -1,5 +1,4 @@
//// [genericArray0.ts]
var x:number[];
@@ -1,19 +1,18 @@
=== tests/cases/compiler/genericArray0.ts ===
var x:number[];
>x : Symbol(x, Decl(genericArray0.ts, 1, 3))
>x : Symbol(x, Decl(genericArray0.ts, 0, 3))
var y = x;
>y : Symbol(y, Decl(genericArray0.ts, 4, 3))
>x : Symbol(x, Decl(genericArray0.ts, 1, 3))
>y : Symbol(y, Decl(genericArray0.ts, 3, 3))
>x : Symbol(x, Decl(genericArray0.ts, 0, 3))
function map<U>() {
>map : Symbol(map, Decl(genericArray0.ts, 4, 10))
>U : Symbol(U, Decl(genericArray0.ts, 6, 13))
>map : Symbol(map, Decl(genericArray0.ts, 3, 10))
>U : Symbol(U, Decl(genericArray0.ts, 5, 13))
var ys: U[] = [];
>ys : Symbol(ys, Decl(genericArray0.ts, 7, 7))
>U : Symbol(U, Decl(genericArray0.ts, 6, 13))
>ys : Symbol(ys, Decl(genericArray0.ts, 6, 7))
>U : Symbol(U, Decl(genericArray0.ts, 5, 13))
}
@@ -1,5 +1,4 @@
=== tests/cases/compiler/genericArray0.ts ===
var x:number[];
>x : number[]
@@ -27,5 +27,4 @@ import j from "./jquery";
=== /src/jquery_user_1.ts ===
import j from "./jquery.js"
>j : Symbol(j, Decl(jquery_user_1.ts, 0, 6))
>j : Symbol(j, Decl(jquery_user_1.ts, 0, 6))
@@ -27,5 +27,4 @@ import j from "./jquery";
=== /src/jquery_user_1.ts ===
import j from "./jquery.js"
>j : number
>j : number
@@ -1,6 +1,42 @@
//// [module_augmentUninstantiatedModule2.ts]
declare var ng: ng.IAngularStatic; declare module ng { export interface IModule { name: string; } export interface IAngularStatic { module: (s: string) => IModule; } } export = ng;
//// [tests/cases/compiler/module_augmentUninstantiatedModule2.ts] ////
//// [module_augmentUninstantiatedModule2.js]
//// [app.ts]
import ng = require("angular");
import "./moduleAugmentation";
var x: number = ng.getNumber();
//// [moduleAugmentation.ts]
import * as ng from "angular"
declare module "angular" {
export interface IAngularStatic {
getNumber: () => number;
}
}
//// [index.d.ts]
declare var ng: ng.IAngularStatic;
declare module ng {
export interface IModule {
name: string;
}
export interface IAngularStatic {
module: (s: string) => IModule;
}
}
export = ng;
//// [moduleAugmentation.js]
"use strict";
module.exports = ng;
exports.__esModule = true;
//// [app.js]
"use strict";
exports.__esModule = true;
var ng = require("angular");
require("./moduleAugmentation");
var x = ng.getNumber();
@@ -1,6 +1,55 @@
=== tests/cases/compiler/module_augmentUninstantiatedModule2.ts ===
declare var ng: ng.IAngularStatic; declare module ng { export interface IModule { name: string; } export interface IAngularStatic { module: (s: string) => IModule; } } export = ng;
>ng : Symbol(ng, Decl(module_augmentUninstantiatedModule2.ts, 0, 11), Decl(module_augmentUninstantiatedModule2.ts, 0, 34))
>ng : Symbol(ng, Decl(module_augmentUninstantiatedModule2.ts, 0, 11), Decl(module_augmentUninstantiatedModule2.ts, 0, 34))
>IAngularStatic : Symbol(ng.IAngularStatic, Decl(module_augmentUninstantiatedModule2.ts, 5, 4))
=== tests/cases/compiler/app.ts ===
import ng = require("angular");
>ng : Symbol(ng, Decl(app.ts, 0, 0))
import "./moduleAugmentation";
var x: number = ng.getNumber();
>x : Symbol(x, Decl(app.ts, 3, 3))
>ng.getNumber : Symbol(ng.IAngularStatic.getNumber, Decl(moduleAugmentation.ts, 2, 37))
>ng : Symbol(ng, Decl(app.ts, 0, 0))
>getNumber : Symbol(ng.IAngularStatic.getNumber, Decl(moduleAugmentation.ts, 2, 37))
=== tests/cases/compiler/moduleAugmentation.ts ===
import * as ng from "angular"
>ng : Symbol(ng, Decl(moduleAugmentation.ts, 0, 6))
declare module "angular" {
export interface IAngularStatic {
>IAngularStatic : Symbol(IAngularStatic, Decl(index.d.ts, 5, 4), Decl(moduleAugmentation.ts, 1, 26))
getNumber: () => number;
>getNumber : Symbol(IAngularStatic.getNumber, Decl(moduleAugmentation.ts, 2, 37))
}
}
=== tests/cases/compiler/node_modules/angular/index.d.ts ===
declare var ng: ng.IAngularStatic;
>ng : Symbol(ng, Decl(index.d.ts, 0, 11), Decl(index.d.ts, 0, 34), Decl(moduleAugmentation.ts, 0, 29))
>ng : Symbol(ng, Decl(index.d.ts, 0, 11), Decl(index.d.ts, 0, 34))
>IAngularStatic : Symbol(IAngularStatic, Decl(index.d.ts, 5, 4))
declare module ng {
>ng : Symbol(ng, Decl(index.d.ts, 0, 11), Decl(index.d.ts, 0, 34), Decl(moduleAugmentation.ts, 0, 29))
export interface IModule {
>IModule : Symbol(IModule, Decl(index.d.ts, 2, 19))
name: string;
>name : Symbol(IModule.name, Decl(index.d.ts, 3, 29))
}
export interface IAngularStatic {
>IAngularStatic : Symbol(IAngularStatic, Decl(index.d.ts, 5, 4), Decl(moduleAugmentation.ts, 1, 26))
module: (s: string) => IModule;
>module : Symbol(IAngularStatic.module, Decl(index.d.ts, 7, 36))
>s : Symbol(s, Decl(index.d.ts, 8, 16))
>IModule : Symbol(IModule, Decl(index.d.ts, 2, 19))
}
}
export = ng;
>ng : Symbol(ng, Decl(index.d.ts, 0, 11), Decl(index.d.ts, 0, 34))
@@ -1,6 +1,56 @@
=== tests/cases/compiler/module_augmentUninstantiatedModule2.ts ===
declare var ng: ng.IAngularStatic; declare module ng { export interface IModule { name: string; } export interface IAngularStatic { module: (s: string) => IModule; } } export = ng;
=== tests/cases/compiler/app.ts ===
import ng = require("angular");
>ng : ng.IAngularStatic
import "./moduleAugmentation";
var x: number = ng.getNumber();
>x : number
>ng.getNumber() : number
>ng.getNumber : () => number
>ng : ng.IAngularStatic
>getNumber : () => number
=== tests/cases/compiler/moduleAugmentation.ts ===
import * as ng from "angular"
>ng : ng.IAngularStatic
declare module "angular" {
export interface IAngularStatic {
>IAngularStatic : IAngularStatic
getNumber: () => number;
>getNumber : () => number
}
}
=== tests/cases/compiler/node_modules/angular/index.d.ts ===
declare var ng: ng.IAngularStatic;
>ng : IAngularStatic
>ng : any
>IAngularStatic : ng.IAngularStatic
>IAngularStatic : IAngularStatic
declare module ng {
>ng : IAngularStatic
export interface IModule {
>IModule : IModule
name: string;
>name : string
}
export interface IAngularStatic {
>IAngularStatic : IAngularStatic
module: (s: string) => IModule;
>module : (s: string) => IModule
>s : string
>IModule : IModule
}
}
export = ng;
>ng : IAngularStatic
@@ -1,15 +1,15 @@
//// [sourceMap-LineBreaks.ts]
var endsWithlineSeparator = 10; 
var endsWithParagraphSeparator = 10; 
var endsWithNextLine = 1;…var endsWithLineFeed = 1;
var endsWithCarriageReturnLineFeed = 1;
var endsWithCarriageReturnLineFeed = 1;
var endsWithCarriageReturn = 1; var endsWithLineFeedCarriageReturn = 1;
var endsWithLineFeedCarriageReturnLineFeed = 1;
var stringLiteralWithLineFeed = "line 1\
line 2";
var stringLiteralWithCarriageReturnLineFeed = "line 1\
line 2";
var stringLiteralWithCarriageReturn = "line 1\ line 2";
line 2";
var stringLiteralWithCarriageReturnLineFeed = "line 1\
line 2";
var stringLiteralWithCarriageReturn = "line 1\ line 2";
var stringLiteralWithLineSeparator = "line 1\
line 2";
var stringLiteralWithParagraphSeparator = "line 1\
line 2";
var stringLiteralWithNextLine = "line 1\…line 2";
//// [sourceMap-LineBreaks.js]
@@ -23,7 +23,7 @@ var endsWithLineFeedCarriageReturn = 1;
var endsWithLineFeedCarriageReturnLineFeed = 1;
var stringLiteralWithLineFeed = "line 1\
line 2";
var stringLiteralWithCarriageReturnLineFeed = "line 1\
var stringLiteralWithCarriageReturnLineFeed = "line 1\
line 2";
var stringLiteralWithCarriageReturn = "line 1\ line 2";
var stringLiteralWithLineSeparator = "line 1\
line 2";
@@ -1,30 +1,47 @@
=== tests/cases/compiler/sourceMap-LineBreaks.ts ===
var endsWithlineSeparator = 10; 
var endsWithParagraphSeparator = 10; 
var endsWithNextLine = 1;…var endsWithLineFeed = 1;
var endsWithlineSeparator = 10;
>endsWithlineSeparator : Symbol(endsWithlineSeparator, Decl(sourceMap-LineBreaks.ts, 0, 3))
var endsWithCarriageReturnLineFeed = 1;
var endsWithParagraphSeparator = 10;
>endsWithParagraphSeparator : Symbol(endsWithParagraphSeparator, Decl(sourceMap-LineBreaks.ts, 1, 3))
var endsWithCarriageReturn = 1; var endsWithLineFeedCarriageReturn = 1;
var endsWithNextLine = 1;…var endsWithLineFeed = 1;
>endsWithNextLine : Symbol(endsWithNextLine, Decl(sourceMap-LineBreaks.ts, 2, 3))
>endsWithLineFeed : Symbol(endsWithLineFeed, Decl(sourceMap-LineBreaks.ts, 2, 29))
var endsWithLineFeedCarriageReturnLineFeed = 1;
var endsWithCarriageReturnLineFeed = 1;
>endsWithCarriageReturnLineFeed : Symbol(endsWithCarriageReturnLineFeed, Decl(sourceMap-LineBreaks.ts, 3, 3))
var endsWithCarriageReturn = 1;
>endsWithCarriageReturn : Symbol(endsWithCarriageReturn, Decl(sourceMap-LineBreaks.ts, 4, 3))
var stringLiteralWithLineFeed = "line 1\
var endsWithLineFeedCarriageReturn = 1;
>endsWithLineFeedCarriageReturn : Symbol(endsWithLineFeedCarriageReturn, Decl(sourceMap-LineBreaks.ts, 5, 3))
var endsWithLineFeedCarriageReturnLineFeed = 1;
>endsWithLineFeedCarriageReturnLineFeed : Symbol(endsWithLineFeedCarriageReturnLineFeed, Decl(sourceMap-LineBreaks.ts, 7, 3))
var stringLiteralWithLineFeed = "line 1\
>stringLiteralWithLineFeed : Symbol(stringLiteralWithLineFeed, Decl(sourceMap-LineBreaks.ts, 9, 3))
line 2";
var stringLiteralWithCarriageReturnLineFeed = "line 1\
>endsWithLineFeedCarriageReturnLineFeed : Symbol(endsWithLineFeedCarriageReturnLineFeed, Decl(sourceMap-LineBreaks.ts, 7, 3))
line 2";
var stringLiteralWithCarriageReturn = "line 1\ line 2";
>stringLiteralWithLineFeed : Symbol(stringLiteralWithLineFeed, Decl(sourceMap-LineBreaks.ts, 9, 3))
var stringLiteralWithLineSeparator = "line 1\
line 2";
var stringLiteralWithParagraphSeparator = "line 1\
line 2";
var stringLiteralWithNextLine = "line 1\…line 2";
>stringLiteralWithCarriageReturnLineFeed : Symbol(stringLiteralWithCarriageReturnLineFeed, Decl(sourceMap-LineBreaks.ts, 11, 3))
line 2";
var stringLiteralWithCarriageReturn = "line 1\
>stringLiteralWithCarriageReturn : Symbol(stringLiteralWithCarriageReturn, Decl(sourceMap-LineBreaks.ts, 13, 3))
line 2";
var stringLiteralWithLineSeparator = "line 1\
>stringLiteralWithLineSeparator : Symbol(stringLiteralWithLineSeparator, Decl(sourceMap-LineBreaks.ts, 16, 3))
line 2";
var stringLiteralWithParagraphSeparator = "line 1\
>stringLiteralWithParagraphSeparator : Symbol(stringLiteralWithParagraphSeparator, Decl(sourceMap-LineBreaks.ts, 18, 3))
line 2";
var stringLiteralWithNextLine = "line 1\…line 2";
>stringLiteralWithNextLine : Symbol(stringLiteralWithNextLine, Decl(sourceMap-LineBreaks.ts, 20, 3))
@@ -1,40 +1,61 @@
=== tests/cases/compiler/sourceMap-LineBreaks.ts ===
var endsWithlineSeparator = 10; 
var endsWithParagraphSeparator = 10; 
var endsWithNextLine = 1;…var endsWithLineFeed = 1;
var endsWithlineSeparator = 10;
>endsWithlineSeparator : number
>10 : 10
var endsWithCarriageReturnLineFeed = 1;
var endsWithParagraphSeparator = 10;
>endsWithParagraphSeparator : number
>10 : 10
var endsWithCarriageReturn = 1; var endsWithLineFeedCarriageReturn = 1;
var endsWithNextLine = 1;…var endsWithLineFeed = 1;
>endsWithNextLine : number
>1 : 1
>endsWithLineFeed : number
>1 : 1
var endsWithLineFeedCarriageReturnLineFeed = 1;
var endsWithCarriageReturnLineFeed = 1;
>endsWithCarriageReturnLineFeed : number
>1 : 1
var endsWithCarriageReturn = 1;
>endsWithCarriageReturn : number
>1 : 1
var stringLiteralWithLineFeed = "line 1\
var endsWithLineFeedCarriageReturn = 1;
>endsWithLineFeedCarriageReturn : number
>1 : 1
line 2";
var stringLiteralWithCarriageReturnLineFeed = "line 1\
var endsWithLineFeedCarriageReturnLineFeed = 1;
>endsWithLineFeedCarriageReturnLineFeed : number
>1 : 1
line 2";
var stringLiteralWithCarriageReturn = "line 1\ line 2";
var stringLiteralWithLineFeed = "line 1\
>stringLiteralWithLineFeed : string
>"line 1\line 2" : "line 1line 2"
var stringLiteralWithLineSeparator = "line 1\
line 2";
var stringLiteralWithParagraphSeparator = "line 1\
line 2";
var stringLiteralWithNextLine = "line 1\…line 2";
line 2";
var stringLiteralWithCarriageReturnLineFeed = "line 1\
>stringLiteralWithCarriageReturnLineFeed : string
>"line 1\line 2" : "line 1line 2"
line 2";
var stringLiteralWithCarriageReturn = "line 1\
>stringLiteralWithCarriageReturn : string
>"line 1\line 2" : "line 1line 2"
line 2";
var stringLiteralWithLineSeparator = "line 1\
>stringLiteralWithLineSeparator : string
>"line 1\
line 2" : "line 1line 2"
line 2";
var stringLiteralWithParagraphSeparator = "line 1\
>stringLiteralWithParagraphSeparator : string
>"line 1\
line 2" : "line 1line 2"
line 2";
var stringLiteralWithNextLine = "line 1\…line 2";
>stringLiteralWithNextLine : string
>"line 1\…line 2" : "line 1\u0085line 2"
@@ -2,6 +2,8 @@
// @includebuiltfile: typescript_standalone.d.ts
// @noImplicitAny:true
// @strictNullChecks:true
// @noTypesBaseline: true
// @noSymbolsBaseline: true
/*
* Note: This test is a public API sample. The sample sources can be found
+2
View File
@@ -2,6 +2,8 @@
// @includebuiltfile: typescript_standalone.d.ts
// @noImplicitAny:true
// @strictNullChecks:true
// @noTypesBaseline: true
// @noSymbolsBaseline: true
/*
* Note: This test is a public API sample. The sample sources can be found
@@ -2,6 +2,8 @@
// @includebuiltfile: typescript_standalone.d.ts
// @noImplicitAny:true
// @strictNullChecks:true
// @noTypesBaseline: true
// @noSymbolsBaseline: true
/*
* Note: This test is a public API sample. The sample sources can be found
@@ -2,6 +2,8 @@
// @includebuiltfile: typescript_standalone.d.ts
// @noImplicitAny:true
// @strictNullChecks:true
// @noTypesBaseline: true
// @noSymbolsBaseline: true
/*
* Note: This test is a public API sample. The sample sources can be found
@@ -2,6 +2,8 @@
// @includebuiltfile: typescript_standalone.d.ts
// @noImplicitAny:true
// @strictNullChecks:true
// @noTypesBaseline: true
// @noSymbolsBaseline: true
/*
* Note: This test is a public API sample. The sample sources can be found
@@ -1,5 +1,6 @@
// @target: ES3
// @sourcemap: true
// @preservelines: true
var endsWithlineSeparator = 10; 
var endsWithParagraphSeparator = 10; 
var endsWithNextLine = 1;…var endsWithLineFeed = 1;
var endsWithCarriageReturnLineFeed = 1;
var endsWithCarriageReturn = 1; var endsWithLineFeedCarriageReturn = 1;
@@ -2,12 +2,10 @@
// @experimentaldecorators: true
// @emitDecoratorMetadata: true
// @module: commonjs
// @filename: a.ts
declare function forwardRef(x: any): any;
declare var Something: any;
@Something({ v: () => Testing123 })
export class Testing123 {
static prop0: string;
static prop1 = Testing123.prop0;
declare var Something: any;
@Something({ v: () => Testing123 })
export class Testing123 {
static prop0: string;
static prop1 = Testing123.prop0;
}