mirror of
https://github.com/microsoft/TypeScript.git
synced 2025-11-18 17:21:48 +00:00
[WIP] harness rewrite
This commit is contained in:
+3
-1
@@ -57,4 +57,6 @@ internal/
|
||||
!tests/cases/projects/NodeModulesSearch/**/*
|
||||
!tests/baselines/reference/project/nodeModules*/**/*
|
||||
.idea
|
||||
yarn.lock
|
||||
yarn.lock
|
||||
package-lock.json
|
||||
.failed-tests
|
||||
Vendored
+8
@@ -18,6 +18,14 @@
|
||||
"problemMatcher": [
|
||||
"$tsc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"taskName": "harness",
|
||||
"isBuildCommand": true,
|
||||
"showOutput": "silent",
|
||||
"problemMatcher": [
|
||||
"$tsc"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
+45
-6
@@ -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
@@ -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");
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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;
|
||||
@@ -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.`);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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, "");
|
||||
}
|
||||
@@ -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";
|
||||
@@ -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, " ");
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
@@ -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();
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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 () { };
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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 };
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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 };
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
@@ -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/");
|
||||
}
|
||||
@@ -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"));
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
+38
-1
@@ -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 {}
|
||||
//}
|
||||
|
||||
+20
@@ -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.
|
||||
+20
@@ -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,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;
|
||||
}
|
||||
Reference in New Issue
Block a user