Merge branch 'master' into fix-missing-enum-member

This commit is contained in:
王文璐
2018-06-26 10:30:09 +08:00
62 changed files with 1883 additions and 470 deletions
+2 -1
View File
@@ -73,4 +73,5 @@ tests/cases/user/*/**/*.d.ts
!tests/cases/user/zone.js/
!tests/cases/user/bignumber.js/
!tests/cases/user/discord.js/
tests/baselines/reference/dt
tests/baselines/reference/dt
.failed-tests
+27 -88
View File
@@ -10,13 +10,8 @@ const insert = require("gulp-insert");
const { append } = require("gulp-insert");
const sourcemaps = require("gulp-sourcemaps");
const del = require("del");
const browserify = require("browserify");
const through2 = require("through2");
const fold = require("travis-fold");
const rename = require("gulp-rename");
const convertMap = require("convert-source-map");
const sorcery = require("sorcery");
const Vinyl = require("vinyl");
const mkdirp = require("./scripts/build/mkdirp");
const gulp = require("./scripts/build/gulp");
const getDirSize = require("./scripts/build/getDirSize");
@@ -28,7 +23,7 @@ const getDiffTool = require("./scripts/build/getDiffTool");
const baselineAccept = require("./scripts/build/baselineAccept");
const cmdLineOptions = require("./scripts/build/options");
const exec = require("./scripts/build/exec");
const _debugMode = require("./scripts/build/debugMode");
const browserify = require("./scripts/build/browserify");
const { libraryTargets, generateLibs } = require("./scripts/build/lib");
const { runConsoleTests, cleanTestDirs, writeTestConfigFile, refBaseline, localBaseline, refRwcBaseline, localRwcBaseline } = require("./scripts/build/tests");
@@ -72,7 +67,7 @@ gulp.task(
"publish-nightly",
"Runs `npm publish --tag next` to create a new nightly build on npm",
["LKG"],
() => runSequence("clean", "useDebugMode", "runtests-parallel",
() => runSequence("clean", "runtests-parallel",
() => exec("npm", ["publish", "--tag", "next"])));
const importDefinitelyTypedTestsProject = "scripts/importDefinitelyTypedTests/tsconfig.json";
@@ -187,6 +182,10 @@ const tscProject = "src/tsc/tsconfig.json";
const tscJs = "built/local/tsc.js";
gulp.task(tscJs, /*help*/ false, [typescriptServicesJs], () => project.compile(tscProject, { typescript: "built" }));
const tscReleaseProject = "src/tsc/tsconfig.release.json";
const tscReleaseJs = "built/local/tsc.release.js";
gulp.task(tscReleaseJs, /*help*/ false, () => project.compile(tscReleaseProject));
const cancellationTokenProject = "src/cancellationToken/tsconfig.json";
const cancellationTokenJs = "built/local/cancellationToken.js";
gulp.task(cancellationTokenJs, /*help*/ false, [typescriptServicesJs], () => project.compile(cancellationTokenProject, { typescript: "built" }));
@@ -258,9 +257,9 @@ gulp.task(
"Generates a Markdown version of the Language Specification",
[specMd]);
gulp.task("produce-LKG", /*help*/ false, ["scripts", "local", cancellationTokenJs, typingsInstallerJs, watchGuardJs], () => {
gulp.task("produce-LKG", /*help*/ false, ["scripts", "local", cancellationTokenJs, typingsInstallerJs, watchGuardJs, tscReleaseJs], () => {
const expectedFiles = [
tscJs,
tscReleaseJs,
typescriptServicesJs,
tsserverJs,
typescriptJs,
@@ -289,7 +288,7 @@ gulp.task("produce-LKG", /*help*/ false, ["scripts", "local", cancellationTokenJ
gulp.task(
"LKG",
"Makes a new LKG out of the built js files",
() => runSequence("clean-built", "dontUseDebugMode", "produce-LKG"));
() => runSequence("clean-built", "produce-LKG"));
// Task to build the tests infrastructure using the built compiler
const testRunnerProject = "src/testRunner/tsconfig.json";
@@ -301,11 +300,6 @@ gulp.task(
"Builds the test infrastructure using the built compiler",
[runJs]);
gulp.task(
"tests-debug",
"Builds the test sources and automation in debug mode",
() => runSequence("useDebugMode", "tests"));
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.",
@@ -325,77 +319,17 @@ gulp.task("clean:" + webTestServerJs, /*help*/ false, () => project.clean(webTes
const bundlePath = path.resolve("built/local/bundle.js");
// TODO(rbuckton): Clean up browserify logic
gulp.task(
"browserify",
"Runs browserify on run.js to produce a file suitable for running tests in the browser",
[runJs],
(done) => {
/** @type {*} */
let originalMap;
/** @type {string} */
let prebundledContent;
browserify(gulp.src([runJs])
.pipe(newer(bundlePath))
.pipe(sourcemaps.init({ loadMaps: true }))
.pipe(through2.obj((file, enc, next) => {
if (originalMap) {
throw new Error("Should only recieve one file!");
}
log(`Saving sourcemaps for ${file.path}`);
originalMap = file.sourceMap;
prebundledContent = file.contents.toString();
// Make paths absolute to help sorcery deal with all the terrible paths being thrown around
originalMap.sources = originalMap.sources.map(s => path.resolve(path.join("src/harness", s)));
// browserify names input files this when they are streamed in, so this is what it puts in the sourcemap
originalMap.file = "built/local/_stream_0.js";
next(/*err*/ undefined, file.contents);
}))
.on("error", err => {
return done(err);
}), { debug: true, basedir: __dirname }) // Attach error handler to inner stream
.bundle((err, contents) => {
if (err) {
if (err.message.match(/Cannot find module '.*_stream_0.js'/)) {
return done(); // Browserify errors when we pass in no files when `newer` filters the input, we should count that as a success, though
}
return done(err);
}
const stringContent = contents.toString();
const file = new Vinyl({ contents, path: bundlePath });
log(`Fixing sourcemaps for ${file.path}`);
// assumes contents is a Buffer, since that's what browserify yields
const maps = convertMap.fromSource(stringContent).toObject();
delete maps.sourceRoot;
maps.sources = maps.sources.map(s => path.resolve(s === "_stream_0.js" ? "built/local/_stream_0.js" : s));
// Strip browserify's inline comments away (could probably just let sorcery do this, but then we couldn't fix the paths)
file.contents = new Buffer(convertMap.removeComments(stringContent));
const chain = sorcery.loadSync(bundlePath, {
content: {
"built/local/_stream_0.js": prebundledContent,
[bundlePath]: stringContent
},
sourcemaps: {
"built/local/_stream_0.js": originalMap,
[bundlePath]: maps,
"node_modules/source-map-support/source-map-support.js": undefined,
}
});
const finalMap = chain.apply();
file.sourceMap = finalMap;
const stream = through2.obj((file, enc, callback) => {
return callback(/*err*/ undefined, file);
});
stream.pipe(sourcemaps.write(".", { includeContent: false }))
.pipe(gulp.dest("."))
.on("end", done)
.on("error", done);
stream.write(file);
stream.end();
});
});
() => gulp.src([runJs], { base: "built/local" })
.pipe(newer(bundlePath))
.pipe(sourcemaps.init({ loadMaps: true }))
.pipe(browserify())
.pipe(rename("bundle.js"))
.pipe(sourcemaps.write(".", /**@type {*}*/({ includeContent: false, destPath: "built/local" })))
.pipe(gulp.dest("built/local")));
gulp.task(
"runtests-browser",
@@ -530,12 +464,17 @@ gulp.task(
"Runs 'local'",
["local"]);
// TODO(rbuckton): Investigate restoring gulp.watch() functionality.
// gulp.task(
// "watch",
// "Watches the src/ directory for changes and executes runtests-parallel.",
// [],
// () => gulp.watch("src/**/*.*", ["runtests-parallel"]));
gulp.task(
"watch-tsc",
"Watches for changes to the build inputs for built/local/tsc.js",
[typescriptServicesJs],
() => project.watch(tscProject, { typescript: "built" }));
gulp.task(
"watch",
"Watches for changes to the build inputs for built/local/run.js executes runtests-parallel.",
[typescriptServicesJs],
() => project.watch(testRunnerProject, { typescript: "built" }, ["runtests-parallel"]));
gulp.task("clean-built", /*help*/ false, ["clean:" + diagnosticInformationMapTs], () => del(["built"]));
gulp.task(
+26 -6
View File
@@ -59,6 +59,7 @@ Paths.builtLocal = "built/local";
Paths.builtLocalCompiler = "built/local/tsc.js";
Paths.builtLocalTSServer = "built/local/tsserver.js";
Paths.builtLocalRun = "built/local/run.js";
Paths.releaseCompiler = "built/local/tsc.release.js";
Paths.typesMapOutput = "built/local/typesMap.json";
Paths.typescriptFile = "built/local/typescript.js";
Paths.servicesFile = "built/local/typescriptServices.js";
@@ -95,6 +96,7 @@ Paths.versionFile = "src/compiler/core.ts";
const ConfigFileFor = {
tsc: "src/tsc",
tscRelease: "src/tsc/tsconfig.release.json",
tsserver: "src/tsserver",
runjs: "src/testRunner",
lint: "scripts/tslint",
@@ -157,6 +159,12 @@ task(TaskNames.scripts, [TaskNames.coreBuild], function() {
});
}, { async: true });
task(Paths.releaseCompiler, function () {
tsbuild([ConfigFileFor.tscRelease], true, () => {
complete();
});
}, { async: true });
// Makes a new LKG. This target does not build anything, but errors if not all the outputs are present in the built/local directory
desc("Makes a new LKG out of the built js files");
task(TaskNames.lkg, [
@@ -165,6 +173,7 @@ task(TaskNames.lkg, [
TaskNames.local,
Paths.servicesDefinitionFile,
Paths.tsserverLibraryDefinitionFile,
Paths.releaseCompiler,
...libraryTargets
], () => {
const sizeBefore = getDirSize(Paths.lkg);
@@ -410,6 +419,8 @@ function runConsoleTests(defaultReporter, runInParallel) {
const runners = process.env.runners || process.env.runner || process.env.ru;
const tests = process.env.test || process.env.tests || process.env.t;
const light = process.env.light === undefined || process.env.light !== "false";
const failed = process.env.failed;
const keepFailed = process.env.keepFailed || failed;
const stackTraceLimit = process.env.stackTraceLimit;
const colorsFlag = process.env.color || process.env.colors;
const colors = colorsFlag !== "false" && colorsFlag !== "0";
@@ -440,8 +451,8 @@ function runConsoleTests(defaultReporter, runInParallel) {
testTimeout = 800000;
}
if (tests || runners || light || testTimeout || taskConfigsFolder) {
writeTestConfigFile(tests, runners, light, taskConfigsFolder, workerCount, stackTraceLimit, colors, testTimeout);
if (tests || runners || light || testTimeout || taskConfigsFolder || keepFailed) {
writeTestConfigFile(tests, runners, light, taskConfigsFolder, workerCount, stackTraceLimit, colors, testTimeout, keepFailed);
}
// timeout normally isn't necessary but Travis-CI has been timing out on compiler baselines occasionally
@@ -449,7 +460,8 @@ function runConsoleTests(defaultReporter, runInParallel) {
if (!runInParallel) {
var startTime = Travis.mark();
var args = [];
args.push("-R", reporter);
args.push("-R", "scripts/failed-tests");
args.push("-O", '"reporter=' + reporter + (keepFailed ? ",keepFailed=true" : "") + '"');
if (tests) args.push("-g", `"${tests}"`);
args.push(colors ? "--colors" : "--no-colors");
if (bail) args.push("--bail");
@@ -460,7 +472,14 @@ function runConsoleTests(defaultReporter, runInParallel) {
}
args.push(Paths.builtLocalRun);
var cmd = "mocha " + args.join(" ");
var cmd;
if (failed) {
args.unshift("scripts/run-failed-tests.js");
cmd = host + " " + args.join(" ");
}
else {
cmd = "mocha " + args.join(" ");
}
var savedNodeEnv = process.env.NODE_ENV;
process.env.NODE_ENV = "development";
exec(cmd, function () {
@@ -521,7 +540,7 @@ function runConsoleTests(defaultReporter, runInParallel) {
}
// used to pass data from jake command line directly to run.js
function writeTestConfigFile(tests, runners, light, taskConfigsFolder, workerCount, stackTraceLimit, colors, testTimeout) {
function writeTestConfigFile(tests, runners, light, taskConfigsFolder, workerCount, stackTraceLimit, colors, testTimeout, keepFailed) {
var testConfigContents = JSON.stringify({
runners: runners ? runners.split(",") : undefined,
test: tests ? [tests] : undefined,
@@ -530,7 +549,8 @@ function writeTestConfigFile(tests, runners, light, taskConfigsFolder, workerCou
taskConfigsFolder: taskConfigsFolder,
stackTraceLimit: stackTraceLimit,
noColor: !colors,
timeout: testTimeout
timeout: testTimeout,
keepFailed: keepFailed
});
fs.writeFileSync('test.config', testConfigContents, { encoding: "utf-8" });
}
+38 -79
View File
@@ -36,9 +36,9 @@
}
},
"@octokit/rest": {
"version": "15.8.2",
"resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-15.8.2.tgz",
"integrity": "sha512-hMUDI6NveJE49rGYfNfXT2CiHODhQMfbqFAa2h8TjR3GrfI1wnfSlsYeGZe4D/Qu+Svqlg9eUisoeIvYWz1yZw==",
"version": "15.9.2",
"resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-15.9.2.tgz",
"integrity": "sha512-UpV9ZTI9ok73E0iFK+LH//c2/WIm6w/FGQ9LFF5GZFANsXut7z75LE0TCcgMZYdCS4eFm525qa3s+0INkPXigA==",
"dev": true,
"requires": {
"before-after-hook": "1.1.0",
@@ -233,9 +233,9 @@
}
},
"@types/mocha": {
"version": "5.2.2",
"resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-5.2.2.tgz",
"integrity": "sha512-tfg9rh2qQhBW6SBqpvfqTgU6lHWGhQURoTrn7NeDF+CEkO9JGYbkzU23EXu6p3bnvDxLeeSX8ohAA23urvWeNw==",
"version": "5.2.3",
"resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-5.2.3.tgz",
"integrity": "sha512-C1wVVr7xhKu6c3Mb27dFzNYR05qvHwgtpN+JOYTGc1pKA7dCEDDYpscn7kul+bCUwa3NoGDbzI1pdznSOa397w==",
"dev": true
},
"@types/node": {
@@ -312,16 +312,6 @@
"@types/node": "8.5.5"
}
},
"JSONStream": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.3.tgz",
"integrity": "sha512-3Sp6WZZ/lXl+nTDoGpGWHEpTnnC6X5fnkolYZR6nwIfzbxxvA8utPWe1gCt7i0m9uVGsSz2IS8K8mJ7HmlduMg==",
"dev": true,
"requires": {
"jsonparse": "1.3.1",
"through": "2.3.8"
}
},
"abbrev": {
"version": "1.0.9",
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.9.tgz",
@@ -786,9 +776,9 @@
"integrity": "sha512-erYug8XoqzU3IfcU8fUgyHqyOXqIE4tUTTQ+7mqUjQlvnXkOO6OlT9c/ZoJVHYoAaqGxr09CN53G7XIsO4KtWA==",
"dev": true,
"requires": {
"JSONStream": "1.3.3",
"combine-source-map": "0.8.0",
"defined": "1.0.0",
"JSONStream": "1.3.3",
"safe-buffer": "5.1.2",
"through2": "2.0.3",
"umd": "3.0.3"
@@ -815,7 +805,6 @@
"integrity": "sha512-fMES05wq1Oukts6ksGUU2TMVHHp06LyQt0SIwbXIHm7waSrQmNBZePsU0iM/4f94zbvb/wHma+D1YrdzWYnF/A==",
"dev": true,
"requires": {
"JSONStream": "1.3.3",
"assert": "1.4.1",
"browser-pack": "6.1.0",
"browser-resolve": "1.11.2",
@@ -837,6 +826,7 @@
"https-browserify": "1.0.0",
"inherits": "2.0.3",
"insert-module-globals": "7.1.0",
"JSONStream": "1.3.3",
"labeled-stream-splicer": "2.0.1",
"mkdirp": "0.5.1",
"module-deps": "6.1.0",
@@ -951,12 +941,6 @@
"ieee754": "1.1.12"
}
},
"buffer-crc32": {
"version": "0.2.13",
"resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz",
"integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=",
"dev": true
},
"buffer-equal": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-1.0.0.tgz",
@@ -1703,12 +1687,6 @@
"es6-symbol": "3.1.1"
}
},
"es6-promise": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.3.1.tgz",
"integrity": "sha1-oIzd6EzNvzTQJ6FFG8kdS80ophM=",
"dev": true
},
"es6-promisify": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz",
@@ -3189,10 +3167,10 @@
"integrity": "sha512-LbYZdybvKjbbcKLp03lB323Cgc8f0iL0Rjh8U6JZ7K1gZSf7MxQH191iCNUcLX4qIQ6/yWe4Q4ZsQ+opcReNFg==",
"dev": true,
"requires": {
"JSONStream": "1.3.3",
"combine-source-map": "0.8.0",
"concat-stream": "1.6.2",
"is-buffer": "1.1.6",
"JSONStream": "1.3.3",
"lexical-scope": "1.2.0",
"path-is-absolute": "1.0.1",
"process": "0.11.10",
@@ -3615,6 +3593,16 @@
"integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=",
"dev": true
},
"JSONStream": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.3.tgz",
"integrity": "sha512-3Sp6WZZ/lXl+nTDoGpGWHEpTnnC6X5fnkolYZR6nwIfzbxxvA8utPWe1gCt7i0m9uVGsSz2IS8K8mJ7HmlduMg==",
"dev": true,
"requires": {
"jsonparse": "1.3.1",
"through": "2.3.8"
}
},
"kew": {
"version": "0.7.0",
"resolved": "https://registry.npmjs.org/kew/-/kew-0.7.0.tgz",
@@ -4095,7 +4083,6 @@
"integrity": "sha512-NPs5N511VD1rrVJihSso/LiBShRbJALYBKzDW91uZYy7BpjnO4bGnZL3HjZ9yKcFdZUWwaYjDz9zxbuP7vKMuQ==",
"dev": true,
"requires": {
"JSONStream": "1.3.3",
"browser-resolve": "1.11.2",
"cached-path-relative": "1.0.1",
"concat-stream": "1.6.2",
@@ -4103,6 +4090,7 @@
"detective": "5.1.0",
"duplexer2": "0.1.4",
"inherits": "2.0.3",
"JSONStream": "1.3.3",
"parents": "1.0.1",
"readable-stream": "2.3.6",
"resolve": "1.7.1",
@@ -5106,26 +5094,6 @@
"ret": "0.1.15"
}
},
"sander": {
"version": "0.5.1",
"resolved": "https://registry.npmjs.org/sander/-/sander-0.5.1.tgz",
"integrity": "sha1-dB4kXiMfB8r7b98PEzrfohalAq0=",
"dev": true,
"requires": {
"es6-promise": "3.3.1",
"graceful-fs": "4.1.11",
"mkdirp": "0.5.1",
"rimraf": "2.6.2"
},
"dependencies": {
"graceful-fs": {
"version": "4.1.11",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz",
"integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=",
"dev": true
}
}
},
"sax": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
@@ -5339,18 +5307,6 @@
}
}
},
"sorcery": {
"version": "0.10.0",
"resolved": "https://registry.npmjs.org/sorcery/-/sorcery-0.10.0.tgz",
"integrity": "sha1-iukK19fLBfxZ8asMY3hF1cFaUrc=",
"dev": true,
"requires": {
"buffer-crc32": "0.2.13",
"minimist": "1.2.0",
"sander": "0.5.1",
"sourcemap-codec": "1.4.1"
}
},
"source-map": {
"version": "0.5.7",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
@@ -5394,12 +5350,6 @@
"integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=",
"dev": true
},
"sourcemap-codec": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.1.tgz",
"integrity": "sha512-hX1eNBNuilj8yfFnECh0DzLgwKpBLMIvmhgEhixXNui8lMLBInTI8Kyxt++RwJnMNu7cAUo635L2+N1TxMJCzA==",
"dev": true
},
"sparkles": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/sparkles/-/sparkles-1.0.0.tgz",
@@ -5532,6 +5482,15 @@
}
}
},
"string_decoder": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"dev": true,
"requires": {
"safe-buffer": "5.1.2"
}
},
"string-width": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
@@ -5559,15 +5518,6 @@
}
}
},
"string_decoder": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"dev": true,
"requires": {
"safe-buffer": "5.1.2"
}
},
"strip-ansi": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
@@ -6180,6 +6130,15 @@
}
}
},
"vinyl-sourcemaps-apply": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/vinyl-sourcemaps-apply/-/vinyl-sourcemaps-apply-0.2.1.tgz",
"integrity": "sha1-q2VJ1h0XLCsbh75cUI0jnI74dwU=",
"dev": true,
"requires": {
"source-map": "0.5.7"
}
},
"vm-browserify": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.0.1.tgz",
+1 -1
View File
@@ -84,13 +84,13 @@
"q": "latest",
"remove-internal": "^2.9.2",
"run-sequence": "latest",
"sorcery": "latest",
"source-map-support": "latest",
"through2": "latest",
"travis-fold": "latest",
"tslint": "latest",
"typescript": "next",
"vinyl": "latest",
"vinyl-sourcemaps-apply": "latest",
"xml2js": "^0.4.19"
},
"scripts": {
+120
View File
@@ -0,0 +1,120 @@
// @ts-check
const Browserify = require("browserify");
const Vinyl = require("vinyl");
const fs = require("fs");
const path = require("path");
const convertMap = require("convert-source-map");
const applySourceMap = require("vinyl-sourcemaps-apply");
const { Transform, Readable } = require("stream");
module.exports = browserify;
/**
* @param {import("browserify").Options} [opts]
*/
function browserify(opts) {
return new Transform({
objectMode: true,
/**
* @param {string | Buffer | File} input
*/
transform(input, _, cb) {
if (typeof input === "string" || Buffer.isBuffer(input)) return cb(new Error("Only Vinyl files are supported."));
try {
const sourceMap = input.sourceMap;
const cwd = input.cwd || process.cwd();
const base = input.base || cwd;
const output = /**@type {File}*/(new Vinyl({ path: input.path, base: input.base }));
const stream = streamFromFile(input);
const b = new Browserify(Object.assign({}, opts, { debug: !!sourceMap, basedir: input.base }));
b.add(stream, { file: input.path, basedir: input.base });
b.bundle((err, contents) => {
if (err) return cb(err);
output.contents = contents;
if (sourceMap) {
output.sourceMap = typeof sourceMap === "string" ? JSON.parse(sourceMap) : sourceMap;
const sourceRoot = output.sourceMap.sourceRoot;
makeAbsoluteSourceMap(cwd, base, output.sourceMap);
const stringContents = contents.toString("utf8");
const newSourceMapConverter = convertMap.fromSource(stringContents);
if (newSourceMapConverter) {
const newSourceMap = newSourceMapConverter.toObject();
makeAbsoluteSourceMap(cwd, base, newSourceMap);
applySourceMap(output, newSourceMap);
makeRelativeSourceMap(cwd, base, sourceRoot, output.sourceMap);
output.contents = new Buffer(convertMap.removeComments(stringContents), "utf8");
}
}
cb(null, output);
});
}
catch (e) {
cb(e);
}
}
});
}
/**
* @param {string | undefined} cwd
* @param {string | undefined} base
* @param {RawSourceMap} sourceMap
*
* @typedef RawSourceMap
* @property {string} version
* @property {string} file
* @property {string} [sourceRoot]
* @property {string[]} sources
* @property {string[]} [sourcesContents]
* @property {string} mappings
* @property {string[]} [names]
*/
function makeAbsoluteSourceMap(cwd = process.cwd(), base = "", sourceMap) {
const sourceRoot = sourceMap.sourceRoot || "";
const resolvedBase = path.resolve(cwd, base);
const resolvedSourceRoot = path.resolve(resolvedBase, sourceRoot);
sourceMap.file = path.resolve(resolvedBase, sourceMap.file).replace(/\\/g, "/");
sourceMap.sources = sourceMap.sources.map(source => path.resolve(resolvedSourceRoot, source).replace(/\\/g, "/"));
sourceMap.sourceRoot = "";
}
/**
* @param {string | undefined} cwd
* @param {string | undefined} base
* @param {string} sourceRoot
* @param {RawSourceMap} sourceMap
*/
function makeRelativeSourceMap(cwd = process.cwd(), base = "", sourceRoot, sourceMap) {
makeAbsoluteSourceMap(cwd, base, sourceMap);
const resolvedBase = path.resolve(cwd, base);
const resolvedSourceRoot = path.resolve(resolvedBase, sourceRoot);
sourceMap.file = path.relative(resolvedBase, sourceMap.file).replace(/\\/g, "/");
sourceMap.sources = sourceMap.sources.map(source => path.relative(resolvedSourceRoot, source).replace(/\\/g, "/"));
sourceMap.sourceRoot = sourceRoot;
}
/**
* @param {File} file
*/
function streamFromFile(file) {
return file.isBuffer() ? streamFromBuffer(file.contents) :
file.isStream() ? file.contents :
fs.createReadStream(file.path, { autoClose: true });
}
/**
* @param {Buffer} buffer
*/
function streamFromBuffer(buffer) {
return new Readable({
read() {
this.push(buffer);
this.push(null);
}
});
}
/**
* @typedef {import("vinyl") & { sourceMap?: any }} File
*/
void 0;
-7
View File
@@ -1,7 +0,0 @@
// @ts-check
const gulp = require("./gulp");
exports.useDebugMode = true;
gulp.task("useDebugMode", /*help*/ false, [], (done) => { exports["useDebugMode"] = true; done(); });
gulp.task("dontUseDebugMode", /*help*/ false, [], (done) => { exports["useDebugMode"] = false; done(); });
+5 -1
View File
@@ -4,7 +4,7 @@ const os = require("os");
/** @type {CommandLineOptions} */
module.exports = minimist(process.argv.slice(2), {
boolean: ["debug", "inspect", "light", "colors", "lint", "soft", "fix"],
boolean: ["debug", "inspect", "light", "colors", "lint", "soft", "fix", "failed", "keepFailed"],
string: ["browser", "tests", "host", "reporter", "stackTraceLimit", "timeout"],
alias: {
"b": "browser",
@@ -32,6 +32,8 @@ module.exports = minimist(process.argv.slice(2), {
lint: process.env.lint || true,
fix: process.env.fix || process.env.f,
workers: process.env.workerCount || os.cpus().length,
failed: false,
keepFailed: false
}
});
@@ -52,6 +54,8 @@ module.exports = minimist(process.argv.slice(2), {
* @property {string} reporter
* @property {string} stackTraceLimit
* @property {string|number} timeout
* @property {boolean} failed
* @property {boolean} keepFailed
*
* @typedef {import("minimist").ParsedArgs & TypedOptions} CommandLineOptions
*/
+527 -70
View File
@@ -16,16 +16,16 @@ const { reportDiagnostics } = require("./diagnostics");
class CompilationGulp extends gulp.Gulp {
/**
* @param {boolean} [verbose]
* @param {boolean} [verbose]
*/
fork(verbose) {
const child = new ForkedGulp(this.tasks);
if (verbose) {
this.on("task_start", e => gulp.emit("task_start", e));
this.on("task_stop", e => gulp.emit("task_stop", e));
this.on("task_err", e => gulp.emit("task_err", e));
this.on("task_not_found", e => gulp.emit("task_not_found", e));
this.on("task_recursion", e => gulp.emit("task_recursion", e));
child.on("task_start", e => gulp.emit("task_start", e));
child.on("task_stop", e => gulp.emit("task_stop", e));
child.on("task_err", e => gulp.emit("task_err", e));
child.on("task_not_found", e => gulp.emit("task_not_found", e));
child.on("task_recursion", e => gulp.emit("task_recursion", e));
}
return child;
}
@@ -58,13 +58,15 @@ const typescriptAliasMap = new Map();
/**
* Defines a gulp orchestration for a TypeScript project, returning a callback that can be used to trigger compilation.
* @param {string} projectSpec The path to a tsconfig.json file or its containing directory.
* @param {ProjectOptions} [options] Project compilation options.
* @param {CompileOptions} [options] Project compilation options.
* @returns {() => Promise<void>}
*/
function createCompiler(projectSpec, options) {
const resolvedOptions = resolveProjectOptions(options);
const resolvedOptions = resolveCompileOptions(options);
const resolvedProjectSpec = resolveProjectSpec(projectSpec, resolvedOptions.paths, /*referrer*/ undefined);
const taskName = compileTaskName(ensureCompileTask(getOrCreateProjectGraph(resolvedProjectSpec, resolvedOptions.paths), resolvedOptions), resolvedOptions.typescript);
const projectGraph = getOrCreateProjectGraph(resolvedProjectSpec, resolvedOptions.paths);
projectGraph.isRoot = true;
const taskName = compileTaskName(ensureCompileTask(projectGraph, resolvedOptions), resolvedOptions.typescript);
return () => new Promise((resolve, reject) => compilationGulp
.fork(resolvedOptions.verbose)
.start(taskName, err => err ? reject(err) : resolve()));
@@ -74,10 +76,10 @@ exports.createCompiler = createCompiler;
/**
* Defines and executes a gulp orchestration for a TypeScript project.
* @param {string} projectSpec The path to a tsconfig.json file or its containing directory.
* @param {ProjectOptions} [options] Project compilation options.
* @param {CompileOptions} [options] Project compilation options.
* @returns {Promise<void>}
*
* @typedef ProjectOptions
*
* @typedef CompileOptions
* @property {string} [cwd] The path to use for the current working directory. Defaults to `process.cwd()`.
* @property {string} [base] The path to use as the base for relative paths. Defaults to `cwd`.
* @property {string} [typescript] A module specifier or path (relative to gulpfile.js) to the version of TypeScript to use.
@@ -86,7 +88,7 @@ exports.createCompiler = createCompiler;
* @property {boolean} [verbose] Indicates whether verbose logging is enabled.
* @property {boolean} [force] Force recompilation (no up-to-date check).
* @property {boolean} [inProcess] Indicates whether to run gulp-typescript in-process or out-of-process (default).
*
*
* @typedef {(stream: NodeJS.ReadableStream) => NodeJS.ReadWriteStream} Hook
*/
function compile(projectSpec, options) {
@@ -103,7 +105,9 @@ exports.compile = compile;
function createCleaner(projectSpec, options) {
const paths = resolvePathOptions(options);
const resolvedProjectSpec = resolveProjectSpec(projectSpec, paths, /*referrer*/ undefined);
const taskName = cleanTaskName(ensureCleanTask(getOrCreateProjectGraph(resolvedProjectSpec, paths)));
const projectGraph = getOrCreateProjectGraph(resolvedProjectSpec, paths);
projectGraph.isRoot = true;
const taskName = cleanTaskName(ensureCleanTask(projectGraph));
return () => new Promise((resolve, reject) => compilationGulp
.fork()
.start(taskName, err => err ? reject(err) : resolve()));
@@ -121,6 +125,25 @@ function clean(projectSpec, options) {
}
exports.clean = clean;
/**
* Defines a watcher to execute a gulp orchestration to recompile a TypeScript project.
* @param {string} projectSpec
* @param {WatchCallback | string[] | CompileOptions} [options]
* @param {WatchCallback | string[]} [tasks]
* @param {WatchCallback} [callback]
*/
function watch(projectSpec, options, tasks, callback) {
if (typeof tasks === "function") callback = tasks, tasks = /**@type {string[] | undefined}*/(undefined);
if (typeof options === "function") callback = options, tasks = /**@type {string[] | undefined}*/(undefined), options = /**@type {CompileOptions | undefined}*/(undefined);
if (Array.isArray(options)) tasks = options, options = /**@type {CompileOptions | undefined}*/(undefined);
const resolvedOptions = resolveCompileOptions(options);
const resolvedProjectSpec = resolveProjectSpec(projectSpec, resolvedOptions.paths, /*referrer*/ undefined);
const projectGraph = getOrCreateProjectGraph(resolvedProjectSpec, resolvedOptions.paths);
projectGraph.isRoot = true;
ensureWatcher(projectGraph, resolvedOptions, tasks, callback);
}
exports.watch = watch;
/**
* Adds a named alias for a TypeScript language service path
* @param {string} alias An alias for a TypeScript version.
@@ -138,7 +161,7 @@ exports.addTypeScript = addTypeScript;
* @param {string} projectSpec The path to a tsconfig.json file or its containing directory.
* @param {string} flattenedProjectSpec The output path for the flattened tsconfig.json file.
* @param {FlattenOptions} [options] Options used to flatten a project hierarchy.
*
*
* @typedef FlattenOptions
* @property {string} [cwd] The path to use for the current working directory. Defaults to `process.cwd()`.
* @property {CompilerOptions} [compilerOptions] Compiler option overrides.
@@ -167,7 +190,7 @@ function flatten(projectSpec, flattenedProjectSpec, options = {}) {
}
/**
* @param {ProjectGraph} projectGraph
* @param {ProjectGraph} projectGraph
*/
function recur(projectGraph) {
if (skipProjects.has(projectGraph)) return;
@@ -190,14 +213,14 @@ exports.flatten = flatten;
* @param {string} typescript An unresolved module specifier to a TypeScript version.
* @param {ResolvedPathOptions} paths Paths used to resolve `typescript`.
* @returns {ResolvedTypeScript}
*
*
* @typedef {string & {_isResolvedTypeScript: never}} ResolvedTypeScriptSpec
*
*
* @typedef ResolvedTypeScript
* @property {ResolvedTypeScriptSpec} typescript
* @property {string} [alias]
*/
function resolveTypeScript(typescript, paths) {
function resolveTypeScript(typescript = "default", paths) {
let alias;
while (typescriptAliasMap.has(typescript)) {
({ typescript, alias, paths } = typescriptAliasMap.get(typescript));
@@ -226,31 +249,34 @@ function getTaskNameSuffix(typescript, paths) {
}
/** @type {ResolvedPathOptions} */
const defaultPaths = { cwd: process.cwd(), base: process.cwd() };
const defaultPaths = (() => {
const cwd = /**@type {AbsolutePath}*/(normalizeSlashes(process.cwd()));
return { cwd, base: cwd };
})();
/**
* @param {PathOptions | undefined} options Path options to resolve and normalize.
* @returns {ResolvedPathOptions}
*
*
* @typedef PathOptions
* @property {string} [cwd] The path to use for the current working directory. Defaults to `process.cwd()`.
* @property {string} [base] The path to use as the base for relative paths. Defaults to `cwd`.
*
*
* @typedef ResolvedPathOptions
* @property {string} cwd The path to use for the current working directory. Defaults to `process.cwd()`.
* @property {string} base The path to use as the base for relative paths. Defaults to `cwd`.
* @property {AbsolutePath} cwd The path to use for the current working directory. Defaults to `process.cwd()`.
* @property {AbsolutePath} base The path to use as the base for relative paths. Defaults to `cwd`.
*/
function resolvePathOptions(options) {
const cwd = options && options.cwd ? path.resolve(process.cwd(), options.cwd) : process.cwd();
const base = options && options.base ? path.resolve(cwd, options.base) : cwd;
const cwd = options && options.cwd ? resolvePath(defaultPaths.cwd, options.cwd) : defaultPaths.cwd;
const base = options && options.base ? resolvePath(cwd, options.base) : cwd;
return cwd === defaultPaths.cwd && base === defaultPaths.base ? defaultPaths : { cwd, base };
}
/**
* @param {ProjectOptions} [options]
* @returns {ResolvedProjectOptions}
*
* @typedef ResolvedProjectOptions
* @param {CompileOptions} [options]
* @returns {ResolvedCompileOptions}
*
* @typedef ResolvedCompileOptions
* @property {ResolvedPathOptions} paths
* @property {ResolvedTypeScript} typescript A resolved reference to a TypeScript implementation.
* @property {Hook} [js] Pipeline hook for .js file outputs.
@@ -259,9 +285,9 @@ function resolvePathOptions(options) {
* @property {boolean} [force] Force recompilation (no up-to-date check).
* @property {boolean} [inProcess] Indicates whether to run gulp-typescript in-process or out-of-process (default).
*/
function resolveProjectOptions(options = {}) {
function resolveCompileOptions(options = {}) {
const paths = resolvePathOptions(options);
const typescript = resolveTypeScript(options.typescript || "default", paths);
const typescript = resolveTypeScript(options.typescript, paths);
return {
paths,
typescript,
@@ -274,13 +300,13 @@ function resolveProjectOptions(options = {}) {
}
/**
* @param {ResolvedProjectOptions} left
* @param {ResolvedProjectOptions} right
* @returns {ResolvedProjectOptions}
* @param {ResolvedCompileOptions} left
* @param {ResolvedCompileOptions} right
* @returns {ResolvedCompileOptions}
*/
function mergeProjectOptions(left, right) {
function mergeCompileOptions(left, right) {
if (left.typescript !== right.typescript) throw new Error("Cannot merge project options targeting different TypeScript packages");
if (tryReuseProjectOptions(left, right)) return left;
if (tryReuseCompileOptions(left, right)) return left;
return {
paths: left.paths,
typescript: left.typescript,
@@ -293,10 +319,10 @@ function mergeProjectOptions(left, right) {
}
/**
* @param {ResolvedProjectOptions} left
* @param {ResolvedProjectOptions} right
* @param {ResolvedCompileOptions} left
* @param {ResolvedCompileOptions} right
*/
function tryReuseProjectOptions(left, right) {
function tryReuseCompileOptions(left, right) {
return left === right
|| left.js === (right.js || left.js)
&& left.dts === (right.dts || left.dts)
@@ -309,11 +335,13 @@ function tryReuseProjectOptions(left, right) {
* @param {ResolvedProjectSpec} projectSpec
* @param {ResolvedPathOptions} paths
* @returns {UnqualifiedProjectName}
*
*
* @typedef {string & {_isUnqualifiedProjectName:never}} UnqualifiedProjectName
*/
function getUnqualifiedProjectName(projectSpec, paths) {
return /**@type {UnqualifiedProjectName}*/(normalizeSlashes(path.relative(paths.base, projectSpec)));
let projectName = path.relative(paths.base, projectSpec);
if (path.basename(projectName) === "tsconfig.json") projectName = path.dirname(projectName);
return /**@type {UnqualifiedProjectName}*/(normalizeSlashes(projectName));
}
/**
@@ -321,16 +349,16 @@ function getUnqualifiedProjectName(projectSpec, paths) {
* @param {ResolvedPathOptions} paths
* @param {ResolvedTypeScript} typescript
* @returns {QualifiedProjectName}
*
*
* @typedef {string & {_isQualifiedProjectName:never}} QualifiedProjectName
*/
function getQualifiedProjectName(projectName, paths, typescript) {
return /**@type {QualifiedProjectName}*/(projectName + getTaskNameSuffix(typescript, paths));
}
/**
/**
* @typedef {import("../../lib/typescript").ParseConfigFileHost} ParseConfigFileHost
* @type {ParseConfigFileHost}
* @type {ParseConfigFileHost}
*/
const parseConfigFileHost = {
useCaseSensitiveFileNames: ts.sys.useCaseSensitiveFileNames,
@@ -342,11 +370,11 @@ const parseConfigFileHost = {
};
/**
* @param {string} [cwd]
* @param {AbsolutePath} [cwd]
* @returns {ParseConfigFileHost}
*/
function getParseConfigFileHost(cwd) {
if (!cwd || cwd === process.cwd()) return parseConfigFileHost;
if (!cwd || cwd === defaultPaths.cwd) return parseConfigFileHost;
return {
useCaseSensitiveFileNames: parseConfigFileHost.useCaseSensitiveFileNames,
fileExists: parseConfigFileHost.fileExists,
@@ -363,14 +391,21 @@ function getParseConfigFileHost(cwd) {
* @returns {ProjectGraph}
*
* @typedef ProjectGraph
* @property {ResolvedPathOptions} paths
* @property {ResolvedProjectSpec} projectSpec The fully qualified path to the tsconfig.json of the project
* @property {UnqualifiedProjectName} projectName The relative project name, excluding any TypeScript suffix.
* @property {string} projectDirectory The fully qualified path to the project directory.
* @property {AbsolutePath} projectDirectory The fully qualified path to the project directory.
* @property {ParsedCommandLine} project The parsed tsconfig.json file.
* @property {ProjectGraphReference[]} references An array of project references.
* @property {Set<ProjectGraph>} referrers An array of referring projects.
* @property {Set<AbsolutePath>} inputs A set of compilation inputs.
* @property {Set<AbsolutePath>} outputs A set of compilation outputs.
* @property {Map<ResolvedTypeScriptSpec, ProjectGraphConfiguration>} configurations TypeScript-specific configurations for the project.
* @property {boolean} cleanTaskCreated A value indicating whether a `clean:` task has been created for this project (not dependent on TypeScript version).
*
* @property {boolean} watcherCreated A value indicating whether a watcher has been created for this project.
* @property {boolean} isRoot The project graph is a root project reference.
* @property {Set<Watcher>} [allWatchers] Tasks to execute when the compilation has completed after being triggered by a watcher.
*
* @typedef ProjectGraphReference
* @property {ProjectGraph} source The referring project.
* @property {ProjectGraph} target The referenced project.
@@ -378,15 +413,22 @@ function getParseConfigFileHost(cwd) {
function getOrCreateProjectGraph(projectSpec, paths) {
let projectGraph = projectGraphCache.get(projectSpec);
if (!projectGraph) {
const project = ts.getParsedCommandLineOfConfigFile(projectSpec, {}, getParseConfigFileHost(paths.cwd));
const project = parseProject(projectSpec, paths);
const projectDirectory = parentDirectory(projectSpec);
projectGraph = {
paths,
projectSpec,
projectName: getUnqualifiedProjectName(projectSpec, paths),
projectDirectory: path.dirname(projectSpec),
projectDirectory,
project,
references: [],
referrers: new Set(),
inputs: new Set(project.fileNames.map(file => resolvePath(projectDirectory, file))),
outputs: new Set(ts.getAllProjectOutputs(project).map(file => resolvePath(projectDirectory, file))),
configurations: new Map(),
cleanTaskCreated: false
cleanTaskCreated: false,
watcherCreated: false,
isRoot: false
};
projectGraphCache.set(projectSpec, projectGraph);
if (project.projectReferences) {
@@ -395,21 +437,80 @@ function getOrCreateProjectGraph(projectSpec, paths) {
const referencedProject = getOrCreateProjectGraph(resolvedProjectSpec, paths);
const reference = { source: projectGraph, target: referencedProject };
projectGraph.references.push(reference);
referencedProject.referrers.add(projectGraph);
}
}
}
return projectGraph;
}
/**
* @param {ResolvedPathOptions} paths
*/
function createParseProject(paths) {
/**
* @param {string} configFilePath
*/
function getProject(configFilePath) {
const projectSpec = resolveProjectSpec(configFilePath, paths, /*referrer*/ undefined);
const projectGraph = getOrCreateProjectGraph(projectSpec, defaultPaths);
return projectGraph && projectGraph.project;
}
return getProject;
}
/**
* @param {ProjectGraph} projectGraph
* @param {ResolvedProjectOptions} resolvedOptions
* @param {ParsedCommandLine} parsedProject
*/
function updateProjectGraph(projectGraph, parsedProject) {
projectGraph.project = parsedProject;
projectGraph.inputs = new Set(projectGraph.project.fileNames.map(file => resolvePath(projectGraph.projectDirectory, file)));
projectGraph.outputs = new Set(ts.getAllProjectOutputs(projectGraph.project).map(file => resolvePath(projectGraph.projectDirectory, file)));
// Update project references.
const oldReferences = new Set(projectGraph.references.map(ref => ref.target));
projectGraph.references = [];
if (projectGraph.project.projectReferences) {
for (const projectReference of projectGraph.project.projectReferences) {
const resolvedProjectSpec = resolveProjectSpec(projectReference.path, projectGraph.paths, projectGraph);
const referencedProject = getOrCreateProjectGraph(resolvedProjectSpec, projectGraph.paths);
const reference = { source: projectGraph, target: referencedProject };
projectGraph.references.push(reference);
referencedProject.referrers.add(projectGraph);
oldReferences.delete(referencedProject);
}
}
// Remove project references that have been removed from the project
for (const referencedProject of oldReferences) {
referencedProject.referrers.delete(projectGraph);
// If there are no more references to this project and the project was not directly requested,
// remove it from the cache.
if (referencedProject.referrers.size === 0 && !referencedProject.isRoot) {
projectGraphCache.delete(referencedProject.projectSpec);
}
}
}
/**
* @param {ResolvedProjectSpec} projectSpec
* @param {ResolvedPathOptions} paths
*/
function parseProject(projectSpec, paths) {
return ts.getParsedCommandLineOfConfigFile(projectSpec, {}, getParseConfigFileHost(paths.cwd));
}
/**
* @param {ProjectGraph} projectGraph
* @param {ResolvedCompileOptions} resolvedOptions
* @returns {ProjectGraphConfiguration}
*
* @typedef ProjectGraphConfiguration
* @property {QualifiedProjectName} projectName
* @property {ResolvedProjectOptions} resolvedOptions
* @property {boolean} compileTaskCreated
* @property {ResolvedCompileOptions} resolvedOptions
* @property {boolean} compileTaskCreated A value indicating whether a `compile:` task has been created for this project.
* @property {Set<Watcher>} [watchers] Tasks to execute when the compilation has completed after being triggered by a watcher.
*/
function getOrCreateProjectGraphConfiguration(projectGraph, resolvedOptions) {
let projectGraphConfig = projectGraph.configurations.get(resolvedOptions.typescript.typescript);
@@ -424,18 +525,56 @@ function getOrCreateProjectGraphConfiguration(projectGraph, resolvedOptions) {
return projectGraphConfig;
}
/**
* Resolves a series of path steps as a normalized, canonical, and absolute path.
* @param {AbsolutePath} basePath
* @param {...string} paths
* @returns {AbsolutePath}
*
* @typedef {string & {_isResolvedPath:never}} AbsolutePath
*/
function resolvePath(basePath, ...paths) {
return /**@type {AbsolutePath}*/(normalizeSlashes(path.resolve(basePath, ...paths)));
}
/**
* @param {AbsolutePath} from
* @param {AbsolutePath} to
* @returns {Path}
*
* @typedef {string & {_isRelativePath:never}} RelativePath
* @typedef {RelativePath | AbsolutePath} Path
*/
function relativePath(from, to) {
let relativePath = normalizeSlashes(path.relative(from, to));
if (!relativePath) relativePath = ".";
if (path.isAbsolute(relativePath)) return /**@type {AbsolutePath}*/(relativePath);
if (relativePath.charAt(0) !== ".") relativePath = "./" + relativePath;
return /**@type {RelativePath}*/(relativePath);
}
/**
* @param {AbsolutePath} file
* @returns {AbsolutePath}
*/
function parentDirectory(file) {
const dirname = path.dirname(file);
if (!dirname || dirname === file) return file;
return /**@type {AbsolutePath}*/(normalizeSlashes(dirname));
}
/**
* @param {string} projectSpec
* @param {ResolvedPathOptions} paths
* @param {ProjectGraph | undefined} referrer
* @returns {ResolvedProjectSpec}
*
* @typedef {string & {_isResolvedProjectSpec: never}} ResolvedProjectSpec
*
* @typedef {AbsolutePath & {_isResolvedProjectSpec: never}} ResolvedProjectSpec
*/
function resolveProjectSpec(projectSpec, paths, referrer) {
projectSpec = path.resolve(paths.cwd, referrer && referrer.projectDirectory || "", projectSpec);
if (!ts.sys.fileExists(projectSpec)) projectSpec = path.join(projectSpec, "tsconfig.json");
return /**@type {ResolvedProjectSpec}*/(normalizeSlashes(projectSpec));
let projectPath = resolvePath(paths.cwd, referrer && referrer.projectDirectory || "", projectSpec);
if (!ts.sys.fileExists(projectPath)) projectPath = resolvePath(paths.cwd, projectPath, "tsconfig.json");
return /**@type {ResolvedProjectSpec}*/(normalizeSlashes(projectPath));
}
/**
@@ -443,24 +582,24 @@ function resolveProjectSpec(projectSpec, paths, referrer) {
* @param {ResolvedPathOptions} paths
*/
function resolveDestPath(projectGraph, paths) {
/** @type {string} */
/** @type {AbsolutePath} */
let destPath = projectGraph.projectDirectory;
if (projectGraph.project.options.outDir) {
destPath = path.resolve(paths.cwd, destPath, projectGraph.project.options.outDir);
destPath = resolvePath(paths.cwd, destPath, projectGraph.project.options.outDir);
}
else if (projectGraph.project.options.outFile || projectGraph.project.options.out) {
destPath = path.dirname(path.resolve(paths.cwd, destPath, projectGraph.project.options.outFile || projectGraph.project.options.out));
destPath = parentDirectory(resolvePath(paths.cwd, destPath, projectGraph.project.options.outFile || projectGraph.project.options.out));
}
return normalizeSlashes(path.relative(paths.base, destPath));
return relativePath(paths.base, destPath);
}
/**
* @param {ProjectGraph} projectGraph
* @param {ResolvedProjectOptions} options
* @param {ResolvedCompileOptions} options
*/
function ensureCompileTask(projectGraph, options) {
const projectGraphConfig = getOrCreateProjectGraphConfiguration(projectGraph, options);
projectGraphConfig.resolvedOptions = options = mergeProjectOptions(options, options);
projectGraphConfig.resolvedOptions = options = mergeCompileOptions(options, options);
if (!projectGraphConfig.compileTaskCreated) {
const deps = makeProjectReferenceCompileTasks(projectGraph, options.typescript, options.paths);
compilationGulp.task(compileTaskName(projectGraph, options.typescript), deps, () => {
@@ -473,7 +612,7 @@ function ensureCompileTask(projectGraph, options) {
? tsc.createProject(configFilePath, { typescript: require(options.typescript.typescript) })
: tsc_oop.createProject(configFilePath, {}, { typescript: options.typescript.typescript });
const stream = project.src()
.pipe(gulpif(!options.force, upToDate(projectGraph.project, { verbose: options.verbose })))
.pipe(gulpif(!options.force, upToDate(projectGraph.project, { verbose: options.verbose, parseProject: createParseProject(options.paths) })))
.pipe(gulpif(sourceMap || inlineSourceMap, sourcemaps.init()))
.pipe(project());
const js = (options.js ? options.js(stream.js) : stream.js)
@@ -528,7 +667,300 @@ function makeProjectReferenceCleanTasks(projectGraph) {
}
/**
* @param {ProjectGraph} projectGraph
* @param {ProjectGraph} projectGraph
* @param {ResolvedCompileOptions} options
* @param {string[]} [tasks]
* @param {(err?: any) => void} [callback]
*
* @typedef Watcher
* @property {string[]} [tasks]
* @property {(err?: any) => void} [callback]
*
* @typedef WatcherRegistration
* @property {() => void} end
*/
function ensureWatcher(projectGraph, options, tasks, callback) {
ensureCompileTask(projectGraph, options);
if (!projectGraph.watcherCreated) {
projectGraph.watcherCreated = true;
makeProjectReferenceWatchers(projectGraph, options.typescript, options.paths);
createWatcher(projectGraph, options, () => {
for (const config of projectGraph.configurations.values()) {
const taskName = compileTaskName(projectGraph, config.resolvedOptions.typescript);
const task = compilationGulp.tasks[taskName];
if (!task) continue;
possiblyTriggerRecompilation(config, task);
}
});
}
if ((tasks && tasks.length) || callback) {
const projectGraphConfig = getOrCreateProjectGraphConfiguration(projectGraph, options);
if (!projectGraphConfig.watchers) projectGraphConfig.watchers = new Set();
if (!projectGraph.allWatchers) projectGraph.allWatchers = new Set();
/** @type {Watcher} */
const watcher = { tasks, callback };
projectGraphConfig.watchers.add(watcher);
projectGraph.allWatchers.add(watcher);
/** @type {WatcherRegistration} */
const registration = {
end() {
projectGraphConfig.watchers.delete(watcher);
projectGraph.allWatchers.delete(watcher);
}
};
return registration;
}
}
/**
* @param {ProjectGraphConfiguration} config
* @param {import("orchestrator").Task} task
*/
function possiblyTriggerRecompilation(config, task) {
// if any of the task's dependencies are still running, wait until they are complete.
for (const dep of task.dep) {
if (compilationGulp.tasks[dep].running) {
setTimeout(possiblyTriggerRecompilation, 50, config, task);
return;
}
}
triggerRecompilation(task, config);
}
/**
* @param {import("orchestrator").Task} task
* @param {ProjectGraphConfiguration} config
*/
function triggerRecompilation(task, config) {
compilationGulp._resetTask(task);
if (config.watchers && config.watchers.size) {
compilationGulp.fork().start(task.name, () => {
/** @type {Set<string>} */
const taskNames = new Set();
/** @type {((err?: any) => void)[]} */
const callbacks = [];
for (const { tasks, callback } of config.watchers) {
if (tasks) for (const task of tasks) taskNames.add(task);
if (callback) callbacks.push(callback);
}
if (taskNames.size) {
gulp.start([...taskNames], error => {
for (const callback of callbacks) callback(error);
});
}
else {
for (const callback of callbacks) callback();
}
});
}
else {
compilationGulp.fork(/*verbose*/ true).start(task.name);
}
}
/**
* @param {ProjectGraph} projectGraph
* @param {ResolvedTypeScript} typescript
* @param {ResolvedPathOptions} paths
*/
function makeProjectReferenceWatchers(projectGraph, typescript, paths) {
for (const { target } of projectGraph.references) {
ensureWatcher(target, { paths, typescript });
}
}
/**
* @param {ProjectGraph} projectGraph
* @param {ResolvedCompileOptions} options
* @param {() => void} callback
*/
function createWatcher(projectGraph, options, callback) {
let projectRemoved = false;
let patterns = collectWatcherPatterns(projectGraph.projectSpec, projectGraph.project, projectGraph);
let watcher = /**@type {GulpWatcher}*/ (gulp.watch(patterns, { cwd: projectGraph.projectDirectory }, onWatchEvent));
/**
* @param {WatchEvent} event
*/
function onWatchEvent(event) {
const file = resolvePath(options.paths.cwd, event.path);
if (file === projectGraph.projectSpec) {
onProjectWatchEvent(event);
}
else {
onInputOrOutputChanged(file);
}
}
/**
* @param {WatchEvent} event
*/
function onProjectWatchEvent(event) {
if (event.type === "renamed" || event.type === "deleted") {
onProjectRenamedOrDeleted();
}
else {
onProjectCreatedOrModified();
}
}
function onProjectRenamedOrDeleted() {
// stop listening for file changes and wait for the project to be created again
projectRemoved = true;
watcher.end();
watcher = /**@type {GulpWatcher}*/ (gulp.watch([projectGraph.projectSpec], onWatchEvent));
}
function onProjectCreatedOrModified() {
const newParsedProject = parseProject(projectGraph.projectSpec, options.paths);
const newPatterns = collectWatcherPatterns(projectGraph.projectSpec, newParsedProject, projectGraph);
if (projectRemoved || !sameValues(patterns, newPatterns)) {
projectRemoved = false;
watcher.end();
updateProjectGraph(projectGraph, newParsedProject);
// Ensure we catch up with any added projects
for (const config of projectGraph.configurations.values()) {
if (config.watchers) {
makeProjectReferenceWatchers(projectGraph, config.resolvedOptions.typescript, config.resolvedOptions.paths);
}
}
patterns = newPatterns;
watcher = /**@type {GulpWatcher}*/ (gulp.watch(patterns, onWatchEvent));
}
onProjectInvalidated();
}
function onProjectInvalidated() {
callback();
}
/**
* @param {AbsolutePath} file
*/
function onInputOrOutputChanged(file) {
if (projectGraph.inputs.has(file) ||
projectGraph.references.some(ref => ref.target.outputs.has(file))) {
onProjectInvalidated();
}
}
}
/**
* @param {ResolvedProjectSpec} projectSpec
* @param {ParsedCommandLine} parsedProject
* @param {ProjectGraph} projectGraph
*/
function collectWatcherPatterns(projectSpec, parsedProject, projectGraph) {
const configFileSpecs = parsedProject.configFileSpecs;
// NOTE: we do not currently handle files from `/// <reference />` tags
const patterns = /**@type {string[]} */([]);
// Add the project contents.
if (configFileSpecs) {
addIncludeSpecs(patterns, configFileSpecs.validatedIncludeSpecs);
addExcludeSpecs(patterns, configFileSpecs.validatedExcludeSpecs);
addIncludeSpecs(patterns, configFileSpecs.filesSpecs);
}
else {
addWildcardDirectories(patterns, parsedProject.wildcardDirectories);
addIncludeSpecs(patterns, parsedProject.fileNames);
}
// Add the project itself.
addIncludeSpec(patterns, projectSpec);
// TODO: Add the project base.
// addExtendsSpec(patterns, project.raw && project.raw.extends);
// Add project reference outputs.
addProjectReferences(patterns, parsedProject.projectReferences);
return patterns;
/**
* @param {string[]} patterns
* @param {string | undefined} includeSpec
*/
function addIncludeSpec(patterns, includeSpec) {
if (!includeSpec) return;
patterns.push(includeSpec);
}
/**
* @param {string[]} patterns
* @param {ReadonlyArray<string> | undefined} includeSpecs
*/
function addIncludeSpecs(patterns, includeSpecs) {
if (!includeSpecs) return;
for (const includeSpec of includeSpecs) {
addIncludeSpec(patterns, includeSpec);
}
}
/**
* @param {string[]} patterns
* @param {string | undefined} excludeSpec
*/
function addExcludeSpec(patterns, excludeSpec) {
if (!excludeSpec) return;
patterns.push("!" + excludeSpec);
}
/**
* @param {string[]} patterns
* @param {ReadonlyArray<string> | undefined} excludeSpecs
*/
function addExcludeSpecs(patterns, excludeSpecs) {
if (!excludeSpecs) return;
for (const excludeSpec of excludeSpecs) {
addExcludeSpec(patterns, excludeSpec);
}
}
/**
* @param {string[]} patterns
* @param {ts.MapLike<ts.WatchDirectoryFlags> | undefined} wildcardDirectories
*/
function addWildcardDirectories(patterns, wildcardDirectories) {
if (!wildcardDirectories) return;
for (const dirname of Object.keys(wildcardDirectories)) {
const flags = wildcardDirectories[dirname];
patterns.push(path.join(dirname, flags & ts.WatchDirectoryFlags.Recursive ? "**" : "", "*"));
}
}
// TODO: Add the project base
// /**
// * @param {string[]} patterns
// * @param {string | undefined} base
// */
// function addExtendsSpec(patterns, base) {
// if (!base) return;
// addIncludeSpec(patterns, base);
// }
/**
* @param {string[]} patterns
* @param {ReadonlyArray<ProjectReference>} projectReferences
*/
function addProjectReferences(patterns, projectReferences) {
if (!projectReferences) return;
for (const projectReference of projectReferences) {
const resolvedProjectSpec = resolveProjectSpec(projectReference.path, projectGraph.paths, projectGraph);
const referencedProject = getOrCreateProjectGraph(resolvedProjectSpec, projectGraph.paths);
for (const output of referencedProject.outputs) {
patterns.push(output);
}
}
}
}
/**
* @param {ProjectGraph} projectGraph
* @param {ResolvedTypeScript} typescript
*/
function compileTaskName(projectGraph, typescript) {
@@ -536,7 +968,7 @@ function compileTaskName(projectGraph, typescript) {
}
/**
* @param {ProjectGraph} projectGraph
* @param {ProjectGraph} projectGraph
*/
function cleanTaskName(projectGraph) {
return `clean:${projectGraph.projectName}`;
@@ -558,7 +990,32 @@ function isPath(moduleSpec) {
}
/**
* @typedef {import("../../lib/typescript").ParsedCommandLine & { options: CompilerOptions }} ParsedCommandLine
* @template T
* @param {ReadonlyArray<T>} left
* @param {ReadonlyArray<T>} right
*/
function sameValues(left, right) {
if (left === right) return true;
if (left.length !== right.length) return false;
for (let i = 0; i < left.length; i++) {
if (left[i] !== right[i]) return false;
}
return true;
}
/**
* @typedef {import("../../lib/typescript").ParsedCommandLine & { options: CompilerOptions, configFileSpecs?: ConfigFileSpecs }} ParsedCommandLine
* @typedef {import("../../lib/typescript").CompilerOptions & { configFilePath?: string }} CompilerOptions
* @typedef {import("../../lib/typescript").ProjectReference} ProjectReference
* @typedef {import("gulp").WatchEvent} WatchEvent
* @typedef {import("gulp").WatchCallback} WatchCallback
* @typedef {NodeJS.EventEmitter & { end(): void, add(files: string | string[], done?: () => void): void, remove(file: string): void }} GulpWatcher
*
* @typedef ConfigFileSpecs
* @property {ReadonlyArray<string> | undefined} filesSpecs
* @property {ReadonlyArray<ProjectReference> | undefined} referenceSpecs
* @property {ReadonlyArray<string> | undefined} validatedIncludeSpecs
* @property {ReadonlyArray<string> | undefined} validatedExcludeSpecs
* @property {ts.MapLike<ts.WatchDirectoryFlags>} wildcardDirectories
*/
void 0;
+22 -26
View File
@@ -35,32 +35,28 @@ function rm(dest, opts) {
*/
write(file, _, cb) {
if (failed) return;
if (Vinyl.isVinyl(file)) {
const basePath = typeof dest === "string" ? path.resolve(cwd, dest) :
typeof dest === "function" ? path.resolve(cwd, dest(file)) :
file.base;
const filePath = path.resolve(basePath, file.relative);
file.cwd = cwd;
file.base = basePath;
file.path = filePath;
const entry = {
file,
deleted: false,
cb,
promise: del(file.path).then(() => {
entry.deleted = true;
processDeleted();
}, err => {
failed = true;
pending.length = 0;
cb(err);
})
};
pending.push(entry);
}
else {
cb(new Error("Only Vinyl files are supported."));
}
if (typeof file === "string" || Buffer.isBuffer(file)) return cb(new Error("Only Vinyl files are supported."));
const basePath = typeof dest === "string" ? path.resolve(cwd, dest) :
typeof dest === "function" ? path.resolve(cwd, dest(file)) :
file.base;
const filePath = path.resolve(basePath, file.relative);
file.cwd = cwd;
file.base = basePath;
file.path = filePath;
const entry = {
file,
deleted: false,
cb,
promise: del(file.path).then(() => {
entry.deleted = true;
processDeleted();
}, err => {
failed = true;
pending.length = 0;
cb(err);
})
};
pending.push(entry);
},
final(cb) {
processDeleted();
+16 -6
View File
@@ -27,14 +27,16 @@ exports.localTest262Baseline = "internal/baselines/test262/local";
*/
function runConsoleTests(runJs, defaultReporter, runInParallel) {
let testTimeout = cmdLineOptions.timeout;
let tests = cmdLineOptions.tests;
const lintFlag = cmdLineOptions.lint;
const debug = cmdLineOptions.debug;
const inspect = cmdLineOptions.inspect;
const tests = cmdLineOptions.tests;
const runners = cmdLineOptions.runners;
const light = cmdLineOptions.light;
const stackTraceLimit = cmdLineOptions.stackTraceLimit;
const testConfigFile = "test.config";
const failed = cmdLineOptions.failed;
const keepFailed = cmdLineOptions.keepFailed || failed;
return cleanTestDirs()
.then(() => {
if (fs.existsSync(testConfigFile)) {
@@ -59,8 +61,8 @@ function runConsoleTests(runJs, defaultReporter, runInParallel) {
testTimeout = 400000;
}
if (tests || runners || light || testTimeout || taskConfigsFolder) {
writeTestConfigFile(tests, runners, light, taskConfigsFolder, workerCount, stackTraceLimit, testTimeout);
if (tests || runners || light || testTimeout || taskConfigsFolder || keepFailed) {
writeTestConfigFile(tests, runners, light, taskConfigsFolder, workerCount, stackTraceLimit, testTimeout, keepFailed);
}
const colors = cmdLineOptions.colors;
@@ -75,7 +77,8 @@ function runConsoleTests(runJs, 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) {
args.push("-R", reporter);
args.push("-R", "scripts/failed-tests");
args.push("-O", '"reporter=' + reporter + (keepFailed ? ",keepFailed=true" : "") + '"');
if (tests) {
args.push("-g", `"${tests}"`);
}
@@ -103,7 +106,12 @@ function runConsoleTests(runJs, defaultReporter, runInParallel) {
args.push(runJs);
}
setNodeEnvToDevelopment();
return exec(host, [runJs]);
if (failed) {
return exec(host, ["scripts/run-failed-tests.js"].concat(args));
}
else {
return exec(host, args);
}
})
.then(({ exitCode }) => {
if (exitCode !== 0) return finish(undefined, exitCode);
@@ -148,8 +156,9 @@ exports.cleanTestDirs = cleanTestDirs;
* @param {string | number} [workerCount]
* @param {string} [stackTraceLimit]
* @param {string | number} [timeout]
* @param {boolean} [keepFailed]
*/
function writeTestConfigFile(tests, runners, light, taskConfigsFolder, workerCount, stackTraceLimit, timeout) {
function writeTestConfigFile(tests, runners, light, taskConfigsFolder, workerCount, stackTraceLimit, timeout, keepFailed) {
const testConfigContents = JSON.stringify({
test: tests ? [tests] : undefined,
runner: runners ? runners.split(",") : undefined,
@@ -159,6 +168,7 @@ function writeTestConfigFile(tests, runners, light, taskConfigsFolder, workerCou
taskConfigsFolder,
noColor: !cmdLineOptions.colors,
timeout,
keepFailed
});
log.info("Running tests with config: " + testConfigContents);
fs.writeFileSync("test.config", testConfigContents);
+6 -6
View File
@@ -15,6 +15,7 @@ const Vinyl = require("vinyl");
*
* @typedef UpToDateOptions
* @property {boolean} [verbose]
* @property {(configFilePath: string) => ParsedCommandLine | undefined} [parseProject]
*/
function upToDate(parsedProject, options) {
/** @type {File[]} */
@@ -31,7 +32,8 @@ function upToDate(parsedProject, options) {
},
getModifiedTime(fileName) {
return getStat(fileName).mtime;
}
},
parseConfigFile: options && options.parseProject
};
const duplex = new Duplex({
objectMode: true,
@@ -39,10 +41,9 @@ function upToDate(parsedProject, options) {
* @param {string|Buffer|File} file
*/
write(file, _, cb) {
if (Vinyl.isVinyl(file)) {
inputs.push(file);
inputMap.set(path.resolve(file.path), file);
}
if (typeof file === "string" || Buffer.isBuffer(file)) return cb(new Error("Only Vinyl files are supported."));
inputs.push(file);
inputMap.set(path.resolve(file.path), file);
cb();
},
final(cb) {
@@ -77,7 +78,6 @@ function upToDate(parsedProject, options) {
module.exports = exports = upToDate;
/**
*
* @param {DiagnosticMessage} message
* @param {...string} args
*/
+22
View File
@@ -0,0 +1,22 @@
import Mocha = require("mocha");
export = FailedTestsReporter;
declare class FailedTestsReporter extends Mocha.reporters.Base {
passes: Mocha.Test[];
failures: Mocha.Test[];
reporterOptions: FailedTestsReporter.ReporterOptions;
reporter?: Mocha.reporters.Base;
constructor(runner: Mocha.Runner, options?: { reporterOptions?: FailedTestsReporter.ReporterOptions });
static writeFailures(file: string, passes: ReadonlyArray<Mocha.Test>, failures: ReadonlyArray<Mocha.Test>, keepFailed: boolean, done: (err?: NodeJS.ErrnoException) => void): void;
done(failures: number, fn?: (failures: number) => void): void;
}
declare namespace FailedTestsReporter {
interface ReporterOptions {
file?: string;
keepFailed?: boolean;
reporter?: string | Mocha.ReporterConstructor;
reporterOptions?: any;
}
}
+117
View File
@@ -0,0 +1,117 @@
// @ts-check
const Mocha = require("mocha");
const path = require("path");
const fs = require("fs");
const os = require("os");
/**
* .failed-tests reporter
*
* @typedef {Object} ReporterOptions
* @property {string} [file]
* @property {boolean} [keepFailed]
* @property {string|Mocha.ReporterConstructor} [reporter]
* @property {*} [reporterOptions]
*/
class FailedTestsReporter extends Mocha.reporters.Base {
/**
* @param {Mocha.Runner} runner
* @param {{ reporterOptions?: ReporterOptions }} [options]
*/
constructor(runner, options) {
super(runner, options);
if (!runner) return;
const reporterOptions = this.reporterOptions = options.reporterOptions || {};
if (reporterOptions.file === undefined) reporterOptions.file = ".failed-tests";
if (reporterOptions.keepFailed === undefined) reporterOptions.keepFailed = false;
if (reporterOptions.reporter) {
/** @type {Mocha.ReporterConstructor} */
let 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 (_) {
reporter = require(path.resolve(process.cwd(), reporterOptions.reporter));
}
}
const newOptions = Object.assign({}, options, { reporterOptions: reporterOptions.reporterOptions || {} });
this.reporter = new reporter(runner, newOptions);
}
/** @type {Mocha.Test[]} */
this.passes = [];
/** @type {Mocha.Test[]} */
this.failures = [];
runner.on("pass", test => this.passes.push(test));
runner.on("fail", test => this.failures.push(test));
}
/**
* @param {string} file
* @param {ReadonlyArray<Mocha.Test>} passes
* @param {ReadonlyArray<Mocha.Test>} failures
* @param {boolean} keepFailed
* @param {(err?: NodeJS.ErrnoException) => void} done
*/
static writeFailures(file, passes, failures, keepFailed, done) {
const failingTests = new Set(fs.existsSync(file) ? readTests() : undefined);
if (failingTests.size > 0) {
for (const test of passes) {
const title = test.fullTitle().trim();
if (title) failingTests.delete(title);
}
}
for (const test of failures) {
const title = test.fullTitle().trim();
if (title) failingTests.add(title);
}
if (failingTests.size > 0) {
const failed = Array.from(failingTests).join(os.EOL);
fs.writeFile(file, failed, "utf8", done);
}
else if (!keepFailed) {
fs.unlink(file, done);
}
else {
done();
}
function readTests() {
return fs.readFileSync(file, "utf8")
.split(/\r?\n/g)
.map(line => line.trim())
.filter(line => line.length > 0);
}
}
/**
* @param {number} failures
* @param {(failures: number) => void} [fn]
*/
done(failures, fn) {
FailedTestsReporter.writeFailures(this.reporterOptions.file, this.passes, this.failures, this.reporterOptions.keepFailed || this.stats.tests === 0, (err) => {
const reporter = this.reporter;
if (reporter && reporter.done) {
reporter.done(failures, fn);
}
else if (fn) {
fn(failures);
}
if (err) console.error(err);
});
}
}
module.exports = FailedTestsReporter;
+3 -8
View File
@@ -17,7 +17,6 @@ async function produceLKG() {
await copyLocalizedDiagnostics();
await buildProtocol();
await copyScriptOutputs();
await buildTsc();
await copyDeclarationOutputs();
await writeGitAttributes();
}
@@ -53,7 +52,7 @@ async function buildProtocol() {
async function copyScriptOutputs() {
await copyWithCopyright("cancellationToken.js");
await copyWithCopyright("tsc.js");
await copyWithCopyright("tsc.release.js", "tsc.js");
await copyWithCopyright("tsserver.js");
await copyWithCopyright("typescript.js");
await copyWithCopyright("typescriptServices.js");
@@ -61,10 +60,6 @@ async function copyScriptOutputs() {
await copyWithCopyright("watchGuard.js");
}
async function buildTsc() {
await exec(path.join(source, "tsc.js"), [`-b -f ${path.join(root, "src/tsc/tsconfig.release.json")}`]);
}
async function copyDeclarationOutputs() {
await copyWithCopyright("tsserverlibrary.d.ts");
await copyWithCopyright("typescript.d.ts");
@@ -75,9 +70,9 @@ async function writeGitAttributes() {
await fs.writeFile(path.join(dest, ".gitattributes"), `* text eol=lf`, "utf-8");
}
async function copyWithCopyright(fileName: string) {
async function copyWithCopyright(fileName: string, destName = fileName) {
const content = await fs.readFile(path.join(source, fileName), "utf-8");
await fs.writeFile(path.join(dest, fileName), copyright + "\n" + content);
await fs.writeFile(path.join(dest, destName), copyright + "\n" + content);
}
async function copyFromBuiltLocal(fileName: string) {
+92
View File
@@ -0,0 +1,92 @@
const spawn = require('child_process').spawn;
const os = require("os");
const fs = require("fs");
const path = require("path");
let grep;
try {
const failedTests = fs.readFileSync(".failed-tests", "utf8");
grep = failedTests
.split(/\r?\n/g)
.map(test => test.trim())
.filter(test => test.length > 0)
.map(escapeRegExp);
}
catch (e) {
grep = [];
}
let args = [];
let waitForGrepValue = false;
let grepIndex = -1;
process.argv.slice(2).forEach((arg, index) => {
const [flag, value] = arg.split('=');
if (flag === "g" || flag === "grep") {
grepIndex = index - 1;
waitForGrepValue = arg !== flag;
if (!waitForGrepValue) grep.push(value.replace(/^"|"$/g, ""));
return;
}
if (waitForGrepValue) {
grep.push(arg.replace(/^"|"$/g, ""));
waitForGrepValue = false;
return;
}
args.push(arg);
});
let mocha = "./node_modules/mocha/bin/mocha";
let grepOption;
let grepOptionValue;
let grepFile;
if (grep.length) {
grepOption = "--grep";
grepOptionValue = grep.join("|");
if (grepOptionValue.length > 20) {
grepFile = path.resolve(os.tmpdir(), ".failed-tests.opts");
fs.writeFileSync(grepFile, `--grep ${grepOptionValue}`, "utf8");
grepOption = "--opts";
grepOptionValue = grepFile;
mocha = "./node_modules/mocha/bin/_mocha";
}
}
if (grepOption) {
if (grepIndex >= 0) {
args.splice(grepIndex, 0, grepOption, grepOptionValue);
}
else {
args.push(grepOption, grepOptionValue);
}
}
args.unshift(path.resolve(mocha));
console.log(args.join(" "));
const proc = spawn(process.execPath, args, {
stdio: 'inherit'
});
proc.on('exit', (code, signal) => {
process.on('exit', () => {
if (grepFile) {
fs.unlinkSync(grepFile);
}
if (signal) {
process.kill(process.pid, signal);
} else {
process.exit(code);
}
});
});
process.on('SIGINT', () => {
proc.kill('SIGINT');
proc.kill('SIGTERM');
});
function escapeRegExp(pattern) {
return pattern
.replace(/[^-\w\d\s]/g, match => "\\" + match)
.replace(/\s/g, "\\s");
}
+4 -4
View File
@@ -4650,15 +4650,15 @@ namespace ts {
let jsDocType: Type | undefined;
for (const declaration of symbol.declarations) {
let declarationInConstructor = false;
const expression = declaration.kind === SyntaxKind.BinaryExpression ? <BinaryExpression>declaration :
declaration.kind === SyntaxKind.PropertyAccessExpression ? cast(declaration.parent, isBinaryExpression) :
const expression = isBinaryExpression(declaration) ? declaration :
isPropertyAccessExpression(declaration) ? isBinaryExpression(declaration.parent) ? declaration.parent : declaration :
undefined;
if (!expression) {
return errorType;
}
const special = getSpecialPropertyAssignmentKind(expression);
const special = isPropertyAccessExpression(expression) ? getSpecialPropertyAccessKind(expression) : getSpecialPropertyAssignmentKind(expression);
if (special === SpecialPropertyAssignmentKind.ThisProperty) {
const thisContainer = getThisContainer(expression, /*includeArrowFunctions*/ false);
// Properties defined in a constructor (or base constructor, or javascript constructor function) don't get undefined added.
@@ -4687,7 +4687,7 @@ namespace ts {
errorNextVariableOrPropertyDeclarationMustHaveSameType(jsDocType, declaration, declarationType);
}
}
else if (!jsDocType) {
else if (!jsDocType && isBinaryExpression(expression)) {
// If we don't have an explicit JSDoc type, get the type from the expression.
let type = getWidenedLiteralType(checkExpressionCached(expression.right));
-17
View File
@@ -1029,23 +1029,6 @@ namespace ts {
return array.slice().sort(comparer);
}
export function best<T>(iter: Iterator<T>, isBetter: (a: T, b: T) => boolean): T | undefined {
const x = iter.next();
if (x.done) {
return undefined;
}
let best = x.value;
while (true) {
const { value, done } = iter.next();
if (done) {
return best;
}
if (isBetter(value, best)) {
best = value;
}
}
}
export function arrayIterator<T>(array: ReadonlyArray<T>): Iterator<T> {
let i = 0;
return { next: () => {
+10 -2
View File
@@ -4419,12 +4419,20 @@
"category": "Message",
"code": 95060
},
"Add missing enum member '{0}'": {
"Convert default export to named export": {
"category": "Message",
"code": 95061
},
"Add all missing enum members": {
"Convert named export to default export": {
"category": "Message",
"code": 95062
},
"Add missing enum member '{0}'": {
"category": "Message",
"code": 95063
},
"Add all missing enum members": {
"category": "Message",
"code": 95064
}
}
+20 -17
View File
@@ -864,13 +864,9 @@ namespace ts {
}
function addJSDocComment<T extends HasJSDoc>(node: T): T {
const comments = getJSDocCommentRanges(node, sourceFile.text);
if (comments) {
for (const comment of comments) {
node.jsDoc = append<JSDoc>(node.jsDoc, JSDocParser.parseJSDocComment(node, comment.pos, comment.end - comment.pos));
}
}
Debug.assert(!node.jsDoc); // Should only be called once per node
const jsDoc = mapDefined(getJSDocCommentRanges(node, sourceFile.text), comment => JSDocParser.parseJSDocComment(node, comment.pos, comment.end - comment.pos));
if (jsDoc.length) node.jsDoc = jsDoc;
return node;
}
@@ -6334,19 +6330,18 @@ namespace ts {
Debug.assert(start <= end);
Debug.assert(end <= content.length);
// Check for /** (JSDoc opening part)
if (!isJSDocLikeText(content, start)) {
return undefined;
}
let tags: JSDocTag[];
let tagsPos: number;
let tagsEnd: number;
const comments: string[] = [];
let result: JSDoc | undefined;
// Check for /** (JSDoc opening part)
if (!isJSDocLikeText(content, start)) {
return result;
}
// + 3 for leading /**, - 5 in total for /** */
scanner.scanRange(start + 3, length - 5, () => {
return scanner.scanRange(start + 3, length - 5, () => {
// Initially we can parse out a tag. We also have seen a starting asterisk.
// This is so that /** * @type */ doesn't parse.
let state = JSDocState.SawAsterisk;
@@ -6432,11 +6427,9 @@ namespace ts {
}
removeLeadingNewlines(comments);
removeTrailingNewlines(comments);
result = createJSDocComment();
return createJSDocComment();
});
return result;
function removeLeadingNewlines(comments: string[]) {
while (comments.length && (comments[0] === "\n" || comments[0] === "\r")) {
comments.shift();
@@ -6574,6 +6567,16 @@ namespace ts {
indent += whitespace.length;
}
break;
case SyntaxKind.OpenBraceToken:
state = JSDocState.SavingComments;
if (lookAhead(() => nextJSDocToken() === SyntaxKind.AtToken && tokenIsIdentifierOrKeyword(nextJSDocToken()) && scanner.getTokenText() === "link")) {
pushComment(scanner.getTokenText());
nextJSDocToken();
pushComment(scanner.getTokenText());
nextJSDocToken();
}
pushComment(scanner.getTokenText());
break;
case SyntaxKind.AsteriskToken:
if (state === JSDocState.BeginningOfLine) {
// leading asterisks start recording on the *next* (non-whitespace) token
+10 -6
View File
@@ -1887,6 +1887,14 @@ namespace ts {
return SpecialPropertyAssignmentKind.None;
}
const lhs = expr.left;
if (isEntityNameExpression(lhs.expression) && lhs.name.escapedText === "prototype" && isObjectLiteralExpression(getInitializerOfBinaryExpression(expr))) {
// F.prototype = { ... }
return SpecialPropertyAssignmentKind.Prototype;
}
return getSpecialPropertyAccessKind(lhs);
}
export function getSpecialPropertyAccessKind(lhs: PropertyAccessExpression): SpecialPropertyAssignmentKind {
if (lhs.expression.kind === SyntaxKind.ThisKeyword) {
return SpecialPropertyAssignmentKind.ThisProperty;
}
@@ -1895,11 +1903,7 @@ namespace ts {
return SpecialPropertyAssignmentKind.ModuleExports;
}
else if (isEntityNameExpression(lhs.expression)) {
if (lhs.name.escapedText === "prototype" && isObjectLiteralExpression(getInitializerOfBinaryExpression(expr))) {
// F.prototype = { ... }
return SpecialPropertyAssignmentKind.Prototype;
}
else if (isPrototypeAccess(lhs.expression)) {
if (isPrototypeAccess(lhs.expression)) {
// F.G....prototype.x = expr
return SpecialPropertyAssignmentKind.PrototypeProperty;
}
@@ -5844,7 +5848,7 @@ namespace ts {
// Keywords
/* @internal */
export function isModifierKind(token: SyntaxKind): boolean {
export function isModifierKind(token: SyntaxKind): token is Modifier["kind"] {
switch (token) {
case SyntaxKind.AbstractKeyword:
case SyntaxKind.AsyncKeyword:
+46 -49
View File
@@ -451,6 +451,12 @@ namespace FourSlash {
this.selectionEnd = end.position;
}
public selectAllInFile(fileName: string) {
this.openFile(fileName);
this.goToPosition(0);
this.selectionEnd = this.activeFile.content.length;
}
public selectRange(range: Range): void {
this.goToRangeStart(range);
this.selectionEnd = range.end;
@@ -850,7 +856,7 @@ namespace FourSlash {
private verifyCompletionsWorker(options: FourSlashInterface.VerifyCompletionsOptions): void {
const actualCompletions = this.getCompletionListAtCaret({ ...options.preferences, triggerCharacter: options.triggerCharacter })!;
if (!actualCompletions) {
if (options.exact === undefined) return;
if ("exact" in options && options.exact === undefined) return;
this.raiseError(`No completions at position '${this.currentCaretPosition}'.`);
}
@@ -1382,17 +1388,8 @@ Actual: ${stringify(fullActual)}`);
}
}
public verifyRenameLocations(startRanges: ArrayOrSingle<Range>, options: Range[] | { findInStrings?: boolean, findInComments?: boolean, ranges: Range[] }) {
let findInStrings: boolean, findInComments: boolean, ranges: Range[];
if (ts.isArray(options)) {
findInStrings = findInComments = false;
ranges = options;
}
else {
findInStrings = !!options.findInStrings;
findInComments = !!options.findInComments;
ranges = options.ranges;
}
public verifyRenameLocations(startRanges: ArrayOrSingle<Range>, options: ReadonlyArray<Range> | { findInStrings?: boolean, findInComments?: boolean, ranges: ReadonlyArray<Range> }) {
const { findInStrings = false, findInComments = false, ranges = this.getRanges() } = ts.isArray(options) ? { findInStrings: false, findInComments: false, ranges: options } : options;
for (const startRange of toArray(startRanges)) {
this.goToRangeStart(startRange);
@@ -1403,30 +1400,12 @@ Actual: ${stringify(fullActual)}`);
break;
}
let references = this.languageService.findRenameLocations(
const references = this.languageService.findRenameLocations(
this.activeFile.fileName, this.currentCaretPosition, findInStrings, findInComments);
ranges = ranges || this.getRanges();
if (!references) {
if (ranges.length !== 0) {
this.raiseError(`Expected ${ranges.length} rename locations; got none.`);
}
return;
}
if (ranges.length !== references.length) {
this.raiseError("Rename location count does not match result.\n\nExpected: " + stringify(ranges) + "\n\nActual:" + stringify(references));
}
ranges = ranges.sort((r1, r2) => r1.pos - r2.pos);
references = references.sort((r1, r2) => r1.textSpan.start - r2.textSpan.start);
ts.zipWith(references, ranges, (reference, range) => {
if (reference.textSpan.start !== range.pos || ts.textSpanEnd(reference.textSpan) !== range.end) {
this.raiseError("Rename location results do not match.\n\nExpected: " + stringify(ranges) + "\n\nActual:" + stringify(references));
}
});
const sort = (locations: ReadonlyArray<ts.RenameLocation> | undefined) =>
locations && ts.sort(locations, (r1, r2) => ts.compareStringsCaseSensitive(r1.fileName, r2.fileName) || r1.textSpan.start - r2.textSpan.start);
assert.deepEqual(sort(references), sort(ranges.map((r): ts.RenameLocation => ({ fileName: r.fileName, textSpan: ts.createTextSpanFromRange(r) }))));
}
}
@@ -3090,9 +3069,22 @@ Actual: ${stringify(fullActual)}`);
this.applyEdits(edit.fileName, edit.textChanges, /*isFormattingEdit*/ false);
}
const { renamePosition, newContent } = parseNewContent();
let renameFilename: string | undefined;
let renamePosition: number | undefined;
this.verifyCurrentFileContent(newContent);
const newFileContents = typeof newContentWithRenameMarker === "string" ? { [this.activeFile.fileName]: newContentWithRenameMarker } : newContentWithRenameMarker;
for (const fileName in newFileContents) {
const { renamePosition: rp, newContent } = TestState.parseNewContent(newFileContents[fileName]);
if (renamePosition === undefined) {
renameFilename = fileName;
renamePosition = rp;
}
else {
ts.Debug.assert(rp === undefined);
}
this.verifyFileContent(fileName, newContent);
}
if (renamePosition === undefined) {
if (editInfo.renameLocation !== undefined) {
@@ -3100,22 +3092,21 @@ Actual: ${stringify(fullActual)}`);
}
}
else {
// TODO: test editInfo.renameFilename value
assert.isDefined(editInfo.renameFilename);
this.assertObjectsEqual(editInfo.renameFilename, renameFilename);
if (renamePosition !== editInfo.renameLocation) {
this.raiseError(`Expected rename position of ${renamePosition}, but got ${editInfo.renameLocation}`);
}
}
}
function parseNewContent(): { renamePosition: number | undefined, newContent: string } {
const renamePosition = newContentWithRenameMarker.indexOf("/*RENAME*/");
if (renamePosition === -1) {
return { renamePosition: undefined, newContent: newContentWithRenameMarker };
}
else {
const newContent = newContentWithRenameMarker.slice(0, renamePosition) + newContentWithRenameMarker.slice(renamePosition + "/*RENAME*/".length);
return { renamePosition, newContent };
}
private static parseNewContent(newContentWithRenameMarker: string): { readonly renamePosition: number | undefined, readonly newContent: string } {
const renamePosition = newContentWithRenameMarker.indexOf("/*RENAME*/");
if (renamePosition === -1) {
return { renamePosition: undefined, newContent: newContentWithRenameMarker };
}
else {
const newContent = newContentWithRenameMarker.slice(0, renamePosition) + newContentWithRenameMarker.slice(renamePosition + "/*RENAME*/".length);
return { renamePosition, newContent };
}
}
@@ -3966,6 +3957,10 @@ namespace FourSlashInterface {
this.state.select(startMarker, endMarker);
}
public selectAllInFile(fileName: string) {
this.state.selectAllInFile(fileName);
}
public selectRange(range: FourSlash.Range): void {
this.state.selectRange(range);
}
@@ -4736,7 +4731,7 @@ namespace FourSlashInterface {
refactorName: string;
actionName: string;
actionDescription: string;
newContent: string;
newContent: NewFileContent;
}
export type ExpectedCompletionEntry = string | {
@@ -4798,9 +4793,11 @@ namespace FourSlashInterface {
filesToSearch?: ReadonlyArray<string>;
}
export type NewFileContent = string | { readonly [filename: string]: string };
export interface NewContentOptions {
// Exactly one of these should be defined.
newFileContent?: string | { readonly [filename: string]: string };
newFileContent?: NewFileContent;
newRangeContent?: string;
}
+1 -2
View File
@@ -30,8 +30,7 @@ interface WeakMap<K extends object, V> {
}
interface WeakMapConstructor {
new (): WeakMap<object, any>;
new <K extends object, V>(entries?: ReadonlyArray<[K, V]> | null): WeakMap<K, V>;
new <K extends object = object, V = any>(entries?: ReadonlyArray<[K, V]> | null): WeakMap<K, V>;
readonly prototype: WeakMap<object, any>;
}
declare var WeakMap: WeakMapConstructor;
@@ -487,7 +487,7 @@
<Str Cat="Text">
<Val><![CDATA[A non-dry build would build project '{0}']]></Val>
<Tgt Cat="Text" Stat="Loc" Orig="New">
<Val><![CDATA[DRY가 아닌 빌드는 프로젝트 '{0}'을(를) 빌드합니다.]]></Val>
<Val><![CDATA[-dry가 아닌 빌드는 프로젝트 '{0}'을(를) 빌드합니다.]]></Val>
</Tgt>
</Str>
<Disp Icon="Str" />
@@ -496,7 +496,7 @@
<Str Cat="Text">
<Val><![CDATA[A non-dry build would delete the following files: {0}]]></Val>
<Tgt Cat="Text" Stat="Loc" Orig="New">
<Val><![CDATA[DRY가 아닌 빌드는 다음 파일을 삭제합니다. {0}]]></Val>
<Val><![CDATA[-dry가 아닌 빌드는 다음 파일을 삭제합니다. {0}]]></Val>
</Tgt>
</Str>
<Disp Icon="Str" />
+15
View File
@@ -692,6 +692,13 @@ namespace ts.server {
return this.findExternalProjectByProjectName(projectName) || this.findConfiguredProjectByProjectName(toNormalizedPath(projectName));
}
/* @internal */
forEachProject(cb: (project: Project) => void) {
for (const p of this.inferredProjects) cb(p);
this.configuredProjects.forEach(cb);
this.externalProjects.forEach(cb);
}
getDefaultProjectForFile(fileName: NormalizedPath, ensureProject: boolean): Project | undefined {
return ensureProject ? this.ensureDefaultProjectForFile(fileName) : this.tryGetDefaultProjectForFile(fileName);
}
@@ -750,6 +757,14 @@ namespace ts.server {
return info && info.getPreferences() || this.hostConfiguration.preferences;
}
getHostFormatCodeOptions(): FormatCodeSettings {
return this.hostConfiguration.formatCodeOptions;
}
getHostPreferences(): UserPreferences {
return this.hostConfiguration.preferences;
}
private onSourceFileChanged(fileName: string, eventKind: FileWatcherEventKind, path: Path) {
const info = this.getScriptInfoForPath(path);
if (!info) {
+2 -3
View File
@@ -627,9 +627,8 @@ namespace ts.server.protocol {
arguments: GetEditsForFileRenameRequestArgs;
}
// Note: The file from FileRequestArgs is just any file in the project.
// We will generate code changes for every file in that project, so the choice is arbitrary.
export interface GetEditsForFileRenameRequestArgs extends FileRequestArgs {
/** Note: Paths may also be directories. */
export interface GetEditsForFileRenameRequestArgs {
readonly oldFilePath: string;
readonly newFilePath: string;
}
+37 -8
View File
@@ -1142,7 +1142,7 @@ namespace ts.server {
return this.getPosition(args, scriptInfo);
}
private getFileAndProject(args: protocol.FileRequestArgs): { file: NormalizedPath, project: Project } {
private getFileAndProject(args: protocol.FileRequestArgs): FileAndProject {
return this.getFileAndProjectWorker(args.file, args.projectFileName);
}
@@ -1738,9 +1738,23 @@ namespace ts.server {
}
private getEditsForFileRename(args: protocol.GetEditsForFileRenameRequestArgs, simplifiedResult: boolean): ReadonlyArray<protocol.FileCodeEdits> | ReadonlyArray<FileTextChanges> {
const { file, project } = this.getFileAndProject(args);
const changes = project.getLanguageService().getEditsForFileRename(toNormalizedPath(args.oldFilePath), toNormalizedPath(args.newFilePath), this.getFormatOptions(file), this.getPreferences(file));
return simplifiedResult ? this.mapTextChangesToCodeEdits(project, changes) : changes;
const oldPath = toNormalizedPath(args.oldFilePath);
const newPath = toNormalizedPath(args.newFilePath);
const formatOptions = this.getHostFormatOptions();
const preferences = this.getHostPreferences();
const changes: (protocol.FileCodeEdits | FileTextChanges)[] = [];
this.projectService.forEachProject(project => {
if (project.isOrphan() || !project.languageServiceEnabled) return;
for (const fileTextChanges of project.getLanguageService().getEditsForFileRename(oldPath, newPath, formatOptions, preferences)) {
// Subsequent projects may make conflicting edits to the same file -- just go with the first.
if (!changes.some(f => f.fileName === fileTextChanges.fileName)) {
changes.push(simplifiedResult ? this.mapTextChangeToCodeEdit(project, fileTextChanges) : fileTextChanges);
}
}
});
return changes as ReadonlyArray<protocol.FileCodeEdits> | ReadonlyArray<FileTextChanges>;
}
private getCodeFixes(args: protocol.CodeFixRequestArgs, simplifiedResult: boolean): ReadonlyArray<protocol.CodeFixAction> | ReadonlyArray<CodeFixAction> | undefined {
@@ -1810,10 +1824,12 @@ namespace ts.server {
}
private mapTextChangesToCodeEdits(project: Project, textChanges: ReadonlyArray<FileTextChanges>): protocol.FileCodeEdits[] {
return textChanges.map(change => {
const path = normalizedPathToPath(toNormalizedPath(change.fileName), this.host.getCurrentDirectory(), fileName => this.getCanonicalFileName(fileName));
return mapTextChangesToCodeEdits(change, project.getSourceFileOrConfigFile(path));
});
return textChanges.map(change => this.mapTextChangeToCodeEdit(project, change));
}
private mapTextChangeToCodeEdit(project: Project, change: FileTextChanges): protocol.FileCodeEdits {
const path = normalizedPathToPath(toNormalizedPath(change.fileName), this.host.getCurrentDirectory(), fileName => this.getCanonicalFileName(fileName));
return mapTextChangesToCodeEdits(change, project.getSourceFileOrConfigFile(path));
}
private convertTextChangeToCodeEdit(change: TextChange, scriptInfo: ScriptInfo): protocol.CodeEdit {
@@ -2303,6 +2319,19 @@ namespace ts.server {
private getPreferences(file: NormalizedPath): UserPreferences {
return this.projectService.getPreferences(file);
}
private getHostFormatOptions(): FormatCodeSettings {
return this.projectService.getHostFormatCodeOptions();
}
private getHostPreferences(): UserPreferences {
return this.projectService.getHostPreferences();
}
}
interface FileAndProject {
readonly file: NormalizedPath;
readonly project: Project;
}
function mapTextChangesToCodeEdits(textChanges: FileTextChanges, sourceFile: SourceFile | undefined): protocol.FileCodeEdits {
+6 -3
View File
@@ -961,8 +961,7 @@ namespace ts.Completions {
break;
case SyntaxKind.BinaryExpression:
if (!((parent as BinaryExpression).left.flags & NodeFlags.ThisNodeHasError)) {
// It has a left-hand side, so we're not in an opening JSX tag.
if (!binaryExpressionMayBeOpenTag(parent as BinaryExpression)) {
break;
}
// falls through
@@ -2256,7 +2255,7 @@ namespace ts.Completions {
return isStringLiteralOrTemplate(contextToken) && position === contextToken.getStart(sourceFile) + 1;
case "<":
// Opening JSX tag
return contextToken.kind === SyntaxKind.LessThanToken && contextToken.parent.kind !== SyntaxKind.BinaryExpression;
return contextToken.kind === SyntaxKind.LessThanToken && (!isBinaryExpression(contextToken.parent) || binaryExpressionMayBeOpenTag(contextToken.parent));
case "/":
return isStringLiteralLike(contextToken)
? !!tryGetImportFromModuleSpecifier(contextToken)
@@ -2266,6 +2265,10 @@ namespace ts.Completions {
}
}
function binaryExpressionMayBeOpenTag({ left }: BinaryExpression): boolean {
return nodeIsMissing(left);
}
function isStringLiteralOrTemplate(node: Node): node is StringLiteralLike | TemplateExpression | TaggedTemplateExpression {
switch (node.kind) {
case SyntaxKind.StringLiteral:
+2 -9
View File
@@ -187,15 +187,8 @@ namespace ts.DocumentHighlights {
});
}
function getModifierOccurrences(modifier: SyntaxKind, declaration: Node): Node[] {
const modifierFlag = modifierToFlag(modifier);
return mapDefined(getNodesToSearchForModifier(declaration, modifierFlag), node => {
if (getModifierFlags(node) & modifierFlag) {
const mod = find(node.modifiers!, m => m.kind === modifier);
Debug.assert(!!mod);
return mod;
}
});
function getModifierOccurrences(modifier: Modifier["kind"], declaration: Node): Node[] {
return mapDefined(getNodesToSearchForModifier(declaration, modifierToFlag(modifier)), node => findModifier(node, modifier));
}
function getNodesToSearchForModifier(declaration: Node, modifierFlag: ModifierFlags): ReadonlyArray<Node> | undefined {
+24
View File
@@ -590,6 +590,30 @@ namespace ts.FindAllReferences.Core {
}
}
export function eachExportReference(
sourceFiles: ReadonlyArray<SourceFile>,
checker: TypeChecker,
cancellationToken: CancellationToken | undefined,
exportSymbol: Symbol,
exportingModuleSymbol: Symbol,
exportName: string,
isDefaultExport: boolean,
cb: (ref: Identifier) => void,
): void {
const importTracker = createImportTracker(sourceFiles, arrayToSet(sourceFiles, f => f.fileName), checker, cancellationToken);
const { importSearches, indirectUsers } = importTracker(exportSymbol, { exportKind: isDefaultExport ? ExportKind.Default : ExportKind.Named, exportingModuleSymbol }, /*isForRename*/ false);
for (const [importLocation] of importSearches) {
cb(importLocation);
}
for (const indirectUser of indirectUsers) {
for (const node of getPossibleSymbolReferenceNodes(indirectUser, isDefaultExport ? "default" : exportName)) {
if (isIdentifier(node) && checker.getSymbolAtLocation(node) === exportSymbol) {
cb(node);
}
}
}
}
function shouldAddSingleReference(singleRef: Identifier | StringLiteral, state: State): boolean {
if (!hasMatchingMeaning(singleRef, state)) return false;
if (!state.options.isForRename) return true;
+1 -1
View File
@@ -99,7 +99,7 @@ namespace ts.GoToDefinition {
*/
function symbolMatchesSignature(s: Symbol, calledDeclaration: SignatureDeclaration) {
return s === calledDeclaration.symbol || s === calledDeclaration.symbol.parent ||
isVariableDeclaration(calledDeclaration.parent) && s === calledDeclaration.parent.symbol;
!isCallLikeExpression(calledDeclaration.parent) && s === calledDeclaration.parent.symbol;
}
export function getReferenceAtPosition(sourceFile: SourceFile, position: number, program: Program): { fileName: string, file: SourceFile } | undefined {
+5 -5
View File
@@ -12,7 +12,7 @@ namespace ts.FindAllReferences {
export type ImportTracker = (exportSymbol: Symbol, exportInfo: ExportInfo, isForRename: boolean) => ImportsResult;
/** Creates the imports map and returns an ImportTracker that uses it. Call this lazily to avoid calling `getDirectImportsMap` unnecessarily. */
export function createImportTracker(sourceFiles: ReadonlyArray<SourceFile>, sourceFilesSet: ReadonlyMap<true>, checker: TypeChecker, cancellationToken: CancellationToken): ImportTracker {
export function createImportTracker(sourceFiles: ReadonlyArray<SourceFile>, sourceFilesSet: ReadonlyMap<true>, checker: TypeChecker, cancellationToken: CancellationToken | undefined): ImportTracker {
const allDirectImports = getDirectImportsMap(sourceFiles, checker, cancellationToken);
return (exportSymbol, exportInfo, isForRename) => {
const { directImports, indirectUsers } = getImportersForExport(sourceFiles, sourceFilesSet, allDirectImports, exportInfo, checker, cancellationToken);
@@ -43,7 +43,7 @@ namespace ts.FindAllReferences {
allDirectImports: Map<ImporterOrCallExpression[]>,
{ exportingModuleSymbol, exportKind }: ExportInfo,
checker: TypeChecker,
cancellationToken: CancellationToken
cancellationToken: CancellationToken | undefined,
): { directImports: Importer[], indirectUsers: ReadonlyArray<SourceFile> } {
const markSeenDirectImport = nodeSeenTracker<ImporterOrCallExpression>();
const markSeenIndirectUser = nodeSeenTracker<SourceFileLike>();
@@ -80,7 +80,7 @@ namespace ts.FindAllReferences {
continue;
}
cancellationToken.throwIfCancellationRequested();
if (cancellationToken) cancellationToken.throwIfCancellationRequested();
switch (direct.kind) {
case SyntaxKind.CallExpression:
@@ -363,11 +363,11 @@ namespace ts.FindAllReferences {
}
/** Returns a map from a module symbol Id to all import statements that directly reference the module. */
function getDirectImportsMap(sourceFiles: ReadonlyArray<SourceFile>, checker: TypeChecker, cancellationToken: CancellationToken): Map<ImporterOrCallExpression[]> {
function getDirectImportsMap(sourceFiles: ReadonlyArray<SourceFile>, checker: TypeChecker, cancellationToken: CancellationToken | undefined): Map<ImporterOrCallExpression[]> {
const map = createMap<ImporterOrCallExpression[]>();
for (const sourceFile of sourceFiles) {
cancellationToken.throwIfCancellationRequested();
if (cancellationToken) cancellationToken.throwIfCancellationRequested();
forEachImport(sourceFile, (importDecl, moduleSpecifier) => {
const moduleSymbol = checker.getSymbolAtLocation(moduleSpecifier);
if (moduleSymbol) {
+11 -7
View File
@@ -263,11 +263,7 @@ namespace ts.JsDoc {
return { newText: singleLineResult, caretOffset: 3 };
}
const posLineAndChar = sourceFile.getLineAndCharacterOfPosition(position);
const lineStart = sourceFile.getLineStarts()[posLineAndChar.line];
// replace non-whitespace characters in prefix with spaces.
const indentationStr = sourceFile.text.substr(lineStart, posLineAndChar.character).replace(/\S/i, () => " ");
const indentationStr = getIndentationStringAtPosition(sourceFile, position);
// A doc comment consists of the following
// * The opening comment line
@@ -276,8 +272,7 @@ namespace ts.JsDoc {
// * TODO: other tags.
// * the closing comment line
// * if the caret was directly in front of the object, then we add an extra line and indentation.
const preamble = "/**" + newLine +
indentationStr + " * ";
const preamble = "/**" + newLine + indentationStr + " * ";
const result =
preamble + newLine +
parameterDocComments(parameters, hasJavaScriptFileExtension(sourceFile.fileName), indentationStr, newLine) +
@@ -287,6 +282,14 @@ namespace ts.JsDoc {
return { newText: result, caretOffset: preamble.length };
}
function getIndentationStringAtPosition(sourceFile: SourceFile, position: number): string {
const { text } = sourceFile;
const lineStart = getLineStartPositionForPosition(position, sourceFile);
let pos = lineStart;
for (; pos <= position && isWhiteSpaceSingleLine(text.charCodeAt(pos)); pos++);
return text.slice(lineStart, pos);
}
function parameterDocComments(parameters: ReadonlyArray<ParameterDeclaration>, isJavaScriptFile: boolean, indentationStr: string, newLine: string): string {
return parameters.map(({ name, dotDotDotToken }, i) => {
const paramName = name.kind === SyntaxKind.Identifier ? name.text : "param" + i;
@@ -303,6 +306,7 @@ namespace ts.JsDoc {
for (let commentOwner = tokenAtPos; commentOwner; commentOwner = commentOwner.parent) {
switch (commentOwner.kind) {
case SyntaxKind.FunctionDeclaration:
case SyntaxKind.FunctionExpression:
case SyntaxKind.MethodDeclaration:
case SyntaxKind.Constructor:
case SyntaxKind.MethodSignature:
+212
View File
@@ -0,0 +1,212 @@
/* @internal */
namespace ts.refactor {
const refactorName = "Convert export";
const actionNameDefaultToNamed = "Convert default export to named export";
const actionNameNamedToDefault = "Convert named export to default export";
registerRefactor(refactorName, {
getAvailableActions(context): ApplicableRefactorInfo[] | undefined {
const info = getInfo(context);
if (!info) return undefined;
const description = info.wasDefault ? Diagnostics.Convert_default_export_to_named_export.message : Diagnostics.Convert_named_export_to_default_export.message;
const actionName = info.wasDefault ? actionNameDefaultToNamed : actionNameNamedToDefault;
return [{ name: refactorName, description, actions: [{ name: actionName, description }] }];
},
getEditsForAction(context, actionName): RefactorEditInfo {
Debug.assert(actionName === actionNameDefaultToNamed || actionName === actionNameNamedToDefault);
const edits = textChanges.ChangeTracker.with(context, t => doChange(context.file, context.program, Debug.assertDefined(getInfo(context)), t, context.cancellationToken));
return { edits, renameFilename: undefined, renameLocation: undefined };
},
});
// If a VariableStatement, will have exactly one VariableDeclaration, with an Identifier for a name.
type ExportToConvert = FunctionDeclaration | ClassDeclaration | InterfaceDeclaration | EnumDeclaration | NamespaceDeclaration | TypeAliasDeclaration | VariableStatement;
interface Info {
readonly exportNode: ExportToConvert;
readonly exportName: Identifier; // This is exportNode.name except for VariableStatement_s.
readonly wasDefault: boolean;
readonly exportingModuleSymbol: Symbol;
}
function getInfo(context: RefactorContext): Info | undefined {
const { file } = context;
const span = getRefactorContextSpan(context);
const token = getTokenAtPosition(file, span.start, /*includeJsDocComment*/ false);
const exportNode = getParentNodeInSpan(token, file, span);
if (!exportNode || (!isSourceFile(exportNode.parent) && !(isModuleBlock(exportNode.parent) && isAmbientModule(exportNode.parent.parent)))) {
return undefined;
}
const exportingModuleSymbol = isSourceFile(exportNode.parent) ? exportNode.parent.symbol : exportNode.parent.parent.symbol;
const flags = getModifierFlags(exportNode);
const wasDefault = !!(flags & ModifierFlags.Default);
// If source file already has a default export, don't offer refactor.
if (!(flags & ModifierFlags.Export) || !wasDefault && exportingModuleSymbol.exports!.has(InternalSymbolName.Default)) {
return undefined;
}
switch (exportNode.kind) {
case SyntaxKind.FunctionDeclaration:
case SyntaxKind.ClassDeclaration:
case SyntaxKind.InterfaceDeclaration:
case SyntaxKind.EnumDeclaration:
case SyntaxKind.TypeAliasDeclaration:
case SyntaxKind.ModuleDeclaration: {
const node = exportNode as FunctionDeclaration | ClassDeclaration | InterfaceDeclaration | EnumDeclaration | TypeAliasDeclaration | NamespaceDeclaration;
return node.name && isIdentifier(node.name) ? { exportNode: node, exportName: node.name, wasDefault, exportingModuleSymbol } : undefined;
}
case SyntaxKind.VariableStatement: {
const vs = exportNode as VariableStatement;
// Must be `export const x = something;`.
if (!(vs.declarationList.flags & NodeFlags.Const) || vs.declarationList.declarations.length !== 1) {
return undefined;
}
const decl = first(vs.declarationList.declarations);
if (!decl.initializer) return undefined;
Debug.assert(!wasDefault);
return isIdentifier(decl.name) ? { exportNode: vs, exportName: decl.name, wasDefault, exportingModuleSymbol } : undefined;
}
default:
return undefined;
}
}
function doChange(exportingSourceFile: SourceFile, program: Program, info: Info, changes: textChanges.ChangeTracker, cancellationToken: CancellationToken | undefined): void {
changeExport(exportingSourceFile, info, changes, program.getTypeChecker());
changeImports(program, info, changes, cancellationToken);
}
function changeExport(exportingSourceFile: SourceFile, { wasDefault, exportNode, exportName }: Info, changes: textChanges.ChangeTracker, checker: TypeChecker): void {
if (wasDefault) {
changes.deleteNode(exportingSourceFile, Debug.assertDefined(findModifier(exportNode, SyntaxKind.DefaultKeyword)));
}
else {
const exportKeyword = Debug.assertDefined(findModifier(exportNode, SyntaxKind.ExportKeyword));
switch (exportNode.kind) {
case SyntaxKind.FunctionDeclaration:
case SyntaxKind.ClassDeclaration:
case SyntaxKind.InterfaceDeclaration:
changes.insertNodeAfter(exportingSourceFile, exportKeyword, createToken(SyntaxKind.DefaultKeyword));
break;
case SyntaxKind.VariableStatement:
// If 'x' isn't used in this file, `export const x = 0;` --> `export default 0;`
if (!FindAllReferences.Core.isSymbolReferencedInFile(exportName, checker, exportingSourceFile)) {
// We checked in `getInfo` that an initializer exists.
changes.replaceNode(exportingSourceFile, exportNode, createExportDefault(Debug.assertDefined(first(exportNode.declarationList.declarations).initializer)));
break;
}
// falls through
case SyntaxKind.EnumDeclaration:
case SyntaxKind.TypeAliasDeclaration:
case SyntaxKind.ModuleDeclaration:
// `export type T = number;` -> `type T = number; export default T;`
changes.deleteModifier(exportingSourceFile, exportKeyword);
changes.insertNodeAfter(exportingSourceFile, exportNode, createExportDefault(createIdentifier(exportName.text)));
break;
default:
Debug.assertNever(exportNode);
}
}
}
function changeImports(program: Program, { wasDefault, exportName, exportingModuleSymbol }: Info, changes: textChanges.ChangeTracker, cancellationToken: CancellationToken | undefined): void {
const checker = program.getTypeChecker();
const exportSymbol = Debug.assertDefined(checker.getSymbolAtLocation(exportName));
FindAllReferences.Core.eachExportReference(program.getSourceFiles(), checker, cancellationToken, exportSymbol, exportingModuleSymbol, exportName.text, wasDefault, ref => {
const importingSourceFile = ref.getSourceFile();
if (wasDefault) {
changeDefaultToNamedImport(importingSourceFile, ref, changes, exportName.text);
}
else {
changeNamedToDefaultImport(importingSourceFile, ref, changes);
}
});
}
function changeDefaultToNamedImport(importingSourceFile: SourceFile, ref: Identifier, changes: textChanges.ChangeTracker, exportName: string): void {
const { parent } = ref;
switch (parent.kind) {
case SyntaxKind.PropertyAccessExpression:
// `a.default` --> `a.foo`
changes.replaceNode(importingSourceFile, ref, createIdentifier(exportName));
break;
case SyntaxKind.ImportSpecifier:
case SyntaxKind.ExportSpecifier: {
const spec = parent as ImportSpecifier | ExportSpecifier;
// `default as foo` --> `foo`, `default as bar` --> `foo as bar`
changes.replaceNode(importingSourceFile, spec, makeImportSpecifier(exportName, spec.name.text));
break;
}
case SyntaxKind.ImportClause: {
const clause = parent as ImportClause;
Debug.assert(clause.name === ref);
const spec = makeImportSpecifier(exportName, ref.text);
const { namedBindings } = clause;
if (!namedBindings) {
// `import foo from "./a";` --> `import { foo } from "./a";`
changes.replaceNode(importingSourceFile, ref, createNamedImports([spec]));
}
else if (namedBindings.kind === SyntaxKind.NamespaceImport) {
// `import foo, * as a from "./a";` --> `import * as a from ".a/"; import { foo } from "./a";`
changes.deleteRange(importingSourceFile, { pos: ref.getStart(importingSourceFile), end: namedBindings.getStart(importingSourceFile) });
const quotePreference = isStringLiteral(clause.parent.moduleSpecifier) ? quotePreferenceFromString(clause.parent.moduleSpecifier, importingSourceFile) : QuotePreference.Double;
const newImport = makeImport(/*default*/ undefined, [makeImportSpecifier(exportName, ref.text)], clause.parent.moduleSpecifier, quotePreference);
changes.insertNodeAfter(importingSourceFile, clause.parent, newImport);
}
else {
// `import foo, { bar } from "./a"` --> `import { bar, foo } from "./a";`
changes.deleteNode(importingSourceFile, ref);
changes.insertNodeAtEndOfList(importingSourceFile, namedBindings.elements, spec);
}
break;
}
default:
Debug.failBadSyntaxKind(parent);
}
}
function changeNamedToDefaultImport(importingSourceFile: SourceFile, ref: Identifier, changes: textChanges.ChangeTracker): void {
const { parent } = ref;
switch (parent.kind) {
case SyntaxKind.PropertyAccessExpression:
// `a.foo` --> `a.default`
changes.replaceNode(importingSourceFile, ref, createIdentifier("default"));
break;
case SyntaxKind.ImportSpecifier:
case SyntaxKind.ExportSpecifier: {
const spec = parent as ImportSpecifier | ExportSpecifier;
if (spec.kind === SyntaxKind.ImportSpecifier) {
// `import { foo } from "./a";` --> `import foo from "./a";`
// `import { foo as bar } from "./a";` --> `import bar from "./a";`
const defaultImport = createIdentifier(spec.name.text);
if (spec.parent.elements.length === 1) {
changes.replaceNode(importingSourceFile, spec.parent, defaultImport);
}
else {
changes.deleteNodeInList(importingSourceFile, spec);
changes.insertNodeBefore(importingSourceFile, spec.parent, defaultImport);
}
}
else {
// `export { foo } from "./a";` --> `export { default as foo } from "./a";`
// `export { foo as bar } from "./a";` --> `export { default as bar } from "./a";`
// `export { foo as default } from "./a";` --> `export { default } from "./a";`
// (Because `export foo from "./a";` isn't valid syntax.)
changes.replaceNode(importingSourceFile, spec, makeExportSpecifier("default", spec.name.text));
}
break;
}
default:
Debug.failBadSyntaxKind(parent);
}
}
function makeImportSpecifier(propertyName: string, name: string): ImportSpecifier {
return createImportSpecifier(propertyName === name ? undefined : createIdentifier(propertyName), createIdentifier(name));
}
function makeExportSpecifier(propertyName: string, name: string): ExportSpecifier {
return createExportSpecifier(propertyName === name ? undefined : createIdentifier(propertyName), createIdentifier(name));
}
}
+1 -1
View File
@@ -1,5 +1,5 @@
/* @internal */
namespace ts.refactor.generateGetAccessorAndSetAccessor {
namespace ts.refactor {
const refactorName = "Convert import";
const actionNameNamespaceToNamed = "Convert namespace import to named imports";
const actionNameNamedToNamespace = "Convert named imports to namespace import";
+2 -1
View File
@@ -1697,7 +1697,8 @@ namespace ts {
}
function findRenameLocations(fileName: string, position: number, findInStrings: boolean, findInComments: boolean): RenameLocation[] | undefined {
return getReferences(fileName, position, { findInStrings, findInComments, isForRename: true });
const refs = getReferences(fileName, position, { findInStrings, findInComments, isForRename: true });
return refs && refs.map(({ fileName, textSpan }): RenameLocation => ({ fileName, textSpan }));
}
function getReferencesAtPosition(fileName: string, position: number): ReferenceEntry[] | undefined {
+14
View File
@@ -241,6 +241,10 @@ namespace ts.textChanges {
return this;
}
public deleteModifier(sourceFile: SourceFile, modifier: Modifier): void {
this.deleteRange(sourceFile, { pos: modifier.getStart(sourceFile), end: skipTrivia(sourceFile.text, modifier.end, /*stopAfterLineBreak*/ true) });
}
public deleteNodeRange(sourceFile: SourceFile, startNode: Node, endNode: Node, options: ConfigurableStartEnd = {}) {
const startPosition = getAdjustedStartPosition(sourceFile, startNode, options, Position.FullStart);
const endPosition = getAdjustedEndPosition(sourceFile, endNode, options);
@@ -397,6 +401,9 @@ namespace ts.textChanges {
else if (isParameter(before)) {
return {};
}
else if (isStringLiteral(before) && isImportDeclaration(before.parent) || isNamedImports(before)) {
return { suffix: ", " };
}
return Debug.failBadSyntaxKind(before); // We haven't handled this kind of node yet -- add it
}
@@ -465,6 +472,10 @@ namespace ts.textChanges {
this.insertNodeAt(sourceFile, endPosition, newNode, this.getInsertNodeAfterOptions(sourceFile, after));
}
public insertNodeAtEndOfList(sourceFile: SourceFile, list: NodeArray<Node>, newNode: Node): void {
this.insertNodeAt(sourceFile, list.end, newNode, { prefix: ", " });
}
public insertNodesAfter(sourceFile: SourceFile, after: Node, newNodes: ReadonlyArray<Node>): void {
const endPosition = this.insertNodeAfterWorker(sourceFile, after, first(newNodes));
this.insertNodesAt(sourceFile, endPosition, newNodes, this.getInsertNodeAfterOptions(sourceFile, after));
@@ -502,6 +513,9 @@ namespace ts.textChanges {
case SyntaxKind.PropertyAssignment:
return { suffix: "," + this.newLineCharacter };
case SyntaxKind.ExportKeyword:
return { prefix: " " };
case SyntaxKind.Parameter:
return {};
+1
View File
@@ -71,6 +71,7 @@
"codefixes/useDefaultImport.ts",
"codefixes/fixAddModuleReferTypeMissingTypeof.ts",
"codefixes/convertToMappedObjectType.ts",
"refactors/convertExport.ts",
"refactors/convertImport.ts",
"refactors/extractSymbol.ts",
"refactors/generateGetAccessorAndSetAccessor.ts",
+10 -2
View File
@@ -1281,13 +1281,17 @@ namespace ts {
export const enum QuotePreference { Single, Double }
export function quotePreferenceFromString(str: StringLiteral, sourceFile: SourceFile): QuotePreference {
return isStringDoubleQuoted(str, sourceFile) ? QuotePreference.Double : QuotePreference.Single;
}
export function getQuotePreference(sourceFile: SourceFile, preferences: UserPreferences): QuotePreference {
if (preferences.quotePreference) {
return preferences.quotePreference === "single" ? QuotePreference.Single : QuotePreference.Double;
}
else {
const firstModuleSpecifier = firstOrUndefined(sourceFile.imports);
return !!firstModuleSpecifier && !isStringDoubleQuoted(firstModuleSpecifier, sourceFile) ? QuotePreference.Single : QuotePreference.Double;
const firstModuleSpecifier = sourceFile.imports && find(sourceFile.imports, isStringLiteral);
return firstModuleSpecifier ? quotePreferenceFromString(firstModuleSpecifier, sourceFile) : QuotePreference.Double;
}
}
@@ -1384,6 +1388,10 @@ namespace ts {
node.getEnd() <= textSpanEnd(span);
}
export function findModifier(node: Node, kind: Modifier["kind"]): Modifier | undefined {
return node.modifiers && find(node.modifiers, m => m.kind === kind);
}
/* @internal */
export function insertImport(changes: textChanges.ChangeTracker, sourceFile: SourceFile, importDecl: Statement): void {
const lastImportDeclaration = findLast(sourceFile.statements, isAnyImportSyntax);
+32 -9
View File
@@ -16,6 +16,10 @@ namespace Harness.Parallel.Host {
const { fork } = require("child_process") as typeof import("child_process");
const { statSync } = require("fs") as typeof import("fs");
// NOTE: paths for module and types for FailedTestReporter _do not_ line up due to our use of --outFile for run.js
// tslint:disable-next-line:variable-name
const FailedTestReporter = require(path.resolve(__dirname, "../../scripts/failed-tests")) as typeof import("../../../scripts/failed-tests");
const perfData = readSavedPerfData(configOption);
const newTasks: Task[] = [];
let tasks: Task[] = [];
@@ -54,7 +58,7 @@ namespace Harness.Parallel.Host {
interface Worker {
process: import("child_process").ChildProcess;
accumulatedOutput: string;
currentTasks?: {file: string}[];
currentTasks?: { file: string }[];
timer?: any;
}
@@ -115,7 +119,7 @@ namespace Harness.Parallel.Host {
update(index: number, percentComplete: number, color: string, title: string | undefined, titleColor?: string) {
percentComplete = minMax(percentComplete, 0, 1);
const progressBar = this._progressBars[index] || (this._progressBars[index] = { });
const progressBar = this._progressBars[index] || (this._progressBars[index] = {});
const width = this._options.width;
const n = Math.floor(width * percentComplete);
const i = width - n;
@@ -177,7 +181,7 @@ namespace Harness.Parallel.Host {
return `${perfdataFileNameFragment}${target ? `.${target}` : ""}.json`;
}
function readSavedPerfData(target?: string): {[testHash: string]: number} | undefined {
function readSavedPerfData(target?: string): { [testHash: string]: number } | undefined {
const perfDataContents = IO.readFile(perfdataFileName(target));
if (perfDataContents) {
return JSON.parse(perfDataContents);
@@ -189,7 +193,7 @@ namespace Harness.Parallel.Host {
return `tsrunner-${runner}://${test}`;
}
function startDelayed(perfData: {[testHash: string]: number} | undefined, totalCost: number) {
function startDelayed(perfData: { [testHash: string]: number } | undefined, totalCost: number) {
console.log(`Discovered ${tasks.length} unittest suites` + (newTasks.length ? ` and ${newTasks.length} new suites.` : "."));
console.log("Discovering runner-based tests...");
const discoverStart = +(new Date());
@@ -247,7 +251,7 @@ namespace Harness.Parallel.Host {
const progressUpdateInterval = 1 / progressBars._options.width;
let nextProgress = progressUpdateInterval;
const newPerfData: {[testHash: string]: number} = {};
const newPerfData: { [testHash: string]: number } = {};
const workers: Worker[] = [];
let closedWorkers = 0;
@@ -531,10 +535,26 @@ namespace Harness.Parallel.Host {
patchStats(consoleReporter.stats);
let xunitReporter: import("mocha").reporters.XUnit | undefined;
if (Utils.getExecutionEnvironment() !== Utils.ExecutionEnvironment.Browser && process.env.CI === "true") {
xunitReporter = new Mocha.reporters.XUnit(replayRunner, { reporterOptions: { suiteName: "Tests", output: "./TEST-results.xml" } });
patchStats(xunitReporter.stats);
xunitReporter.write(`<?xml version="1.0" encoding="UTF-8"?>\n`);
let failedTestReporter: import("../../../scripts/failed-tests") | undefined;
if (Utils.getExecutionEnvironment() !== Utils.ExecutionEnvironment.Browser) {
if (process.env.CI === "true") {
xunitReporter = new Mocha.reporters.XUnit(replayRunner, {
reporterOptions: {
suiteName: "Tests",
output: "./TEST-results.xml"
}
});
patchStats(xunitReporter.stats);
xunitReporter.write(`<?xml version="1.0" encoding="UTF-8"?>\n`);
}
else {
failedTestReporter = new FailedTestReporter(replayRunner, {
reporterOptions: {
file: path.resolve(".failed-tests"),
keepFailed
}
});
}
}
const savedUseColors = Base.useColors;
@@ -551,6 +571,9 @@ namespace Harness.Parallel.Host {
if (xunitReporter) {
xunitReporter.done(errorResults.length, failures => process.exit(failures));
}
else if (failedTestReporter) {
failedTestReporter.done(errorResults.length, failures => process.exit(failures));
}
else {
process.exit(errorResults.length);
}
+5
View File
@@ -62,6 +62,7 @@ let workerCount: number;
let runUnitTests: boolean | undefined;
let stackTraceLimit: number | "full" | undefined;
let noColors = false;
let keepFailed = false;
interface TestConfig {
light?: boolean;
@@ -74,6 +75,7 @@ interface TestConfig {
runUnitTests?: boolean;
noColors?: boolean;
timeout?: number;
keepFailed?: boolean;
}
interface TaskSet {
@@ -102,6 +104,9 @@ function handleTestConfig() {
if (testConfig.noColors !== undefined) {
noColors = testConfig.noColors;
}
if (testConfig.keepFailed) {
keepFailed = true;
}
if (testConfig.stackTraceLimit === "full") {
(<any>Error).stackTraceLimit = Infinity;
+6
View File
@@ -308,6 +308,12 @@ namespace ts {
* @param {object} o Doc doc
* @param {string} o.f Doc for f
*/`);
parsesCorrectly("",
`/**
* {@link first link}
* Inside {@link link text} thing
* @see {@link second link text} and {@link Foo|a foo} as well.
*/`);
});
});
describe("getFirstToken", () => {
@@ -421,6 +421,18 @@ namespace ts.projectSystem {
return createTextSpan(start, substring.length);
}
function protocolTextSpanFromSubstring(str: string, substring: string): protocol.TextSpan {
const start = str.indexOf(substring);
Debug.assert(start !== -1);
const lineStarts = computeLineStarts(str);
const toLocation = (pos: number) => lineAndCharacterToLocation(computeLineAndCharacterOfPosition(lineStarts, pos));
return { start: toLocation(start), end: toLocation(start + substring.length) };
}
function lineAndCharacterToLocation(lc: LineAndCharacter): protocol.Location {
return { line: lc.line + 1, offset: lc.character + 1 };
}
/**
* Test server cancellation token used to mock host token cancellation requests.
* The cancelAfterRequest constructor param specifies how many isCancellationRequested() calls
@@ -464,14 +476,13 @@ namespace ts.projectSystem {
}
}
export function makeSessionRequest<T>(command: string, args: T) {
const newRequest: protocol.Request = {
export function makeSessionRequest<T>(command: string, args: T): protocol.Request {
return {
seq: 0,
type: "request",
command,
arguments: args
};
return newRequest;
}
export function openFilesForSession(files: ReadonlyArray<File>, session: server.Session) {
@@ -8682,6 +8693,49 @@ export const x = 10;`
}],
}]);
});
it("works with multiple projects", () => {
const aUserTs: File = {
path: "/a/user.ts",
content: 'import { x } from "./old";',
};
const aOldTs: File = {
path: "/a/old.ts",
content: "export const x = 0;",
};
const aTsconfig: File = {
path: "/a/tsconfig.json",
content: "{}",
};
const bUserTs: File = {
path: "/b/user.ts",
content: 'import { x } from "../a/old";',
};
const bTsconfig: File = {
path: "/b/tsconfig.json",
content: "{}",
};
const host = createServerHost([aUserTs, aOldTs, aTsconfig, bUserTs, bTsconfig]);
const session = createSession(host);
openFilesForSession([aUserTs, bUserTs], session);
const renameRequest = makeSessionRequest<protocol.GetEditsForFileRenameRequestArgs>(CommandNames.GetEditsForFileRename, {
oldFilePath: "/a/old.ts",
newFilePath: "/a/new.ts",
});
const response = session.executeCommand(renameRequest).response as protocol.GetEditsForFileRenameResponse["body"];
assert.deepEqual(response, [
{
fileName: aUserTs.path,
textChanges: [{ ...protocolTextSpanFromSubstring(aUserTs.content, "./old"), newText: "./new" }],
},
{
fileName: bUserTs.path,
textChanges: [{ ...protocolTextSpanFromSubstring(bUserTs.content, "../a/old"), newText: "../a/new" }],
},
]);
});
});
describe("tsserverProjectSystem document registry in project service", () => {
+1 -1
View File
@@ -1,7 +1,7 @@
{
"extends": "../tsconfig-base",
"compilerOptions": {
"outFile": "../../lib/tsc.js",
"outFile": "../../built/local/tsc.release.js",
"stripInternal": true,
"preserveConstEnums": false,
"declaration": false,
@@ -0,0 +1,28 @@
{
"kind": "JSDocComment",
"pos": 0,
"end": 127,
"tags": {
"0": {
"kind": "JSDocTag",
"pos": 63,
"end": 68,
"atToken": {
"kind": "AtToken",
"pos": 63,
"end": 64
},
"tagName": {
"kind": "Identifier",
"pos": 64,
"end": 67,
"escapedText": "see"
},
"comment": "{@link second link text} and {@link Foo|a foo} as well."
},
"length": 1,
"pos": 63,
"end": 68
},
"comment": "{@link first link}\nInside {@link link text} thing"
}
+20 -5
View File
@@ -225,7 +225,6 @@ declare namespace ts {
* Returns a new sorted array.
*/
function sort<T>(array: ReadonlyArray<T>, comparer: Comparer<T>): T[];
function best<T>(iter: Iterator<T>, isBetter: (a: T, b: T) => boolean): T | undefined;
function arrayIterator<T>(array: ReadonlyArray<T>): Iterator<T>;
/**
* Stable sort of an array. Elements equal to each other maintain their relative position in the array.
@@ -5916,6 +5915,8 @@ declare namespace ts {
Remove_braces_from_arrow_function: DiagnosticMessage;
Add_missing_enum_member_0: DiagnosticMessage;
Add_all_missing_enum_members: DiagnosticMessage;
Convert_default_export_to_named_export: DiagnosticMessage;
Convert_named_export_to_default_export: DiagnosticMessage;
};
}
declare namespace ts {
@@ -6263,6 +6264,7 @@ declare namespace ts {
function isExportsIdentifier(node: Node): boolean;
function isModuleExportsPropertyAccessExpression(node: Node): boolean;
function getSpecialPropertyAssignmentKind(expr: BinaryExpression): SpecialPropertyAssignmentKind;
function getSpecialPropertyAccessKind(lhs: PropertyAccessExpression): SpecialPropertyAssignmentKind;
function getInitializerOfBinaryExpression(expr: BinaryExpression): Expression;
function isPrototypePropertyAssignment(node: Node): boolean;
function isSpecialPropertyDeclaration(expr: PropertyAccessExpression): boolean;
@@ -6950,7 +6952,7 @@ declare namespace ts {
function isTemplateMiddleOrTemplateTail(node: Node): node is TemplateMiddle | TemplateTail;
function isStringTextContainingNode(node: Node): node is StringLiteral | TemplateLiteralToken;
function isGeneratedIdentifier(node: Node): node is GeneratedIdentifier;
function isModifierKind(token: SyntaxKind): boolean;
function isModifierKind(token: SyntaxKind): token is Modifier["kind"];
function isParameterPropertyModifier(kind: SyntaxKind): boolean;
function isClassMemberModifier(idToken: SyntaxKind): boolean;
function isModifier(node: Node): node is Modifier;
@@ -10784,6 +10786,7 @@ declare namespace ts {
Single = 0,
Double = 1
}
function quotePreferenceFromString(str: StringLiteral, sourceFile: SourceFile): QuotePreference;
function getQuotePreference(sourceFile: SourceFile, preferences: UserPreferences): QuotePreference;
function symbolNameNoDefault(symbol: Symbol): string | undefined;
function symbolEscapedNameNoDefault(symbol: Symbol): __String | undefined;
@@ -10808,6 +10811,7 @@ declare namespace ts {
some(pred: (node: Node) => boolean): boolean;
}
function getParentNodeInSpan(node: Node | undefined, file: SourceFile, span: TextSpan): Node | undefined;
function findModifier(node: Node, kind: Modifier["kind"]): Modifier | undefined;
function insertImport(changes: textChanges.ChangeTracker, sourceFile: SourceFile, importDecl: Statement): void;
}
declare namespace ts {
@@ -10985,7 +10989,7 @@ declare namespace ts.FindAllReferences {
}
type ImportTracker = (exportSymbol: Symbol, exportInfo: ExportInfo, isForRename: boolean) => ImportsResult;
/** Creates the imports map and returns an ImportTracker that uses it. Call this lazily to avoid calling `getDirectImportsMap` unnecessarily. */
function createImportTracker(sourceFiles: ReadonlyArray<SourceFile>, sourceFilesSet: ReadonlyMap<true>, checker: TypeChecker, cancellationToken: CancellationToken): ImportTracker;
function createImportTracker(sourceFiles: ReadonlyArray<SourceFile>, sourceFilesSet: ReadonlyMap<true>, checker: TypeChecker, cancellationToken: CancellationToken | undefined): ImportTracker;
/** Info about an exported symbol to perform recursive search on. */
interface ExportInfo {
exportingModuleSymbol: Symbol;
@@ -11088,6 +11092,7 @@ declare namespace ts.FindAllReferences {
declare namespace ts.FindAllReferences.Core {
/** Core find-all-references algorithm. Handles special cases before delegating to `getReferencedSymbolsForSymbol`. */
function getReferencedSymbolsForNode(position: number, node: Node, program: Program, sourceFiles: ReadonlyArray<SourceFile>, cancellationToken: CancellationToken, options?: Options, sourceFilesSet?: ReadonlyMap<true>): SymbolAndEntries[] | undefined;
function eachExportReference(sourceFiles: ReadonlyArray<SourceFile>, checker: TypeChecker, cancellationToken: CancellationToken | undefined, exportSymbol: Symbol, exportingModuleSymbol: Symbol, exportName: string, isDefaultExport: boolean, cb: (ref: Identifier) => void): void;
/** Used as a quick check for whether a symbol is used at all in a file (besides its definition). */
function isSymbolReferencedInFile(definition: Identifier, checker: TypeChecker, sourceFile: SourceFile): boolean;
function eachSymbolReferenceInFile<T>(definition: Identifier, checker: TypeChecker, sourceFile: SourceFile, cb: (token: Identifier) => T): T | undefined;
@@ -11473,6 +11478,7 @@ declare namespace ts.textChanges {
deleteRange(sourceFile: SourceFile, range: TextRange): this;
/** Warning: This deletes comments too. See `copyComments` in `convertFunctionToEs6Class`. */
deleteNode(sourceFile: SourceFile, node: Node, options?: ConfigurableStartEnd): this;
deleteModifier(sourceFile: SourceFile, modifier: Modifier): void;
deleteNodeRange(sourceFile: SourceFile, startNode: Node, endNode: Node, options?: ConfigurableStartEnd): this;
deleteNodeRangeExcludingEnd(sourceFile: SourceFile, startNode: Node, afterEndNode: Node | undefined, options?: ConfigurableStartEnd): void;
deleteNodeInList(sourceFile: SourceFile, node: Node): this;
@@ -11504,6 +11510,7 @@ declare namespace ts.textChanges {
private getInsertNodeAtClassStartPrefixSuffix;
insertNodeAfterComma(sourceFile: SourceFile, after: Node, newNode: Node): void;
insertNodeAfter(sourceFile: SourceFile, after: Node, newNode: Node): void;
insertNodeAtEndOfList(sourceFile: SourceFile, list: NodeArray<Node>, newNode: Node): void;
insertNodesAfter(sourceFile: SourceFile, after: Node, newNodes: ReadonlyArray<Node>): void;
private insertNodeAfterWorker;
private getInsertNodeAfterOptions;
@@ -11661,7 +11668,9 @@ declare namespace ts.codefix {
}
declare namespace ts.codefix {
}
declare namespace ts.refactor.generateGetAccessorAndSetAccessor {
declare namespace ts.refactor {
}
declare namespace ts.refactor {
}
declare namespace ts.refactor.extractSymbol {
/**
@@ -12458,7 +12467,7 @@ declare namespace ts.server.protocol {
command: CommandTypes.GetEditsForFileRename;
arguments: GetEditsForFileRenameRequestArgs;
}
interface GetEditsForFileRenameRequestArgs extends FileRequestArgs {
interface GetEditsForFileRenameRequestArgs {
readonly oldFilePath: string;
readonly newFilePath: string;
}
@@ -13830,6 +13839,7 @@ declare namespace ts.server {
private delayUpdateProjectGraphs;
setCompilerOptionsForInferredProjects(projectCompilerOptions: protocol.ExternalProjectCompilerOptions, projectRootPath?: string): void;
findProject(projectName: string): Project | undefined;
forEachProject(cb: (project: Project) => void): void;
getDefaultProjectForFile(fileName: NormalizedPath, ensureProject: boolean): Project | undefined;
tryGetDefaultProjectForFile(fileName: NormalizedPath): Project | undefined;
ensureDefaultProjectForFile(fileName: NormalizedPath): Project;
@@ -13838,6 +13848,8 @@ declare namespace ts.server {
private ensureProjectStructuresUptoDate;
getFormatCodeOptions(file: NormalizedPath): FormatCodeSettings;
getPreferences(file: NormalizedPath): UserPreferences;
getHostFormatCodeOptions(): FormatCodeSettings;
getHostPreferences(): UserPreferences;
private onSourceFileChanged;
private handleDeletedFile;
watchWildcardDirectory(directory: Path, flags: WatchDirectoryFlags, project: ConfiguredProject): FileWatcher;
@@ -14068,6 +14080,7 @@ declare namespace ts.server {
private mapCodeAction;
private mapCodeFixAction;
private mapTextChangesToCodeEdits;
private mapTextChangeToCodeEdit;
private convertTextChangeToCodeEdit;
private getBraceMatching;
private getDiagnosticsForProject;
@@ -14084,6 +14097,8 @@ declare namespace ts.server {
onMessage(message: string): void;
private getFormatOptions;
private getPreferences;
private getHostFormatOptions;
private getHostPreferences;
}
interface HandlerResponse {
response?: {};
@@ -0,0 +1,16 @@
=== /a.js ===
function C() { this.x = false; };
>C : Symbol(C, Decl(a.js, 0, 0))
>x : Symbol(C.x, Decl(a.js, 0, 14), Decl(a.js, 0, 33))
/** @type {number} */
C.prototype.x;
>C.prototype : Symbol(Function.prototype, Decl(lib.es5.d.ts, --, --))
>C : Symbol(C, Decl(a.js, 0, 0))
>prototype : Symbol(Function.prototype, Decl(lib.es5.d.ts, --, --))
new C().x;
>new C().x : Symbol(C.x, Decl(a.js, 0, 14), Decl(a.js, 0, 33))
>C : Symbol(C, Decl(a.js, 0, 0))
>x : Symbol(C.x, Decl(a.js, 0, 14), Decl(a.js, 0, 33))
@@ -0,0 +1,23 @@
=== /a.js ===
function C() { this.x = false; };
>C : typeof C
>this.x = false : false
>this.x : any
>this : any
>x : any
>false : false
/** @type {number} */
C.prototype.x;
>C.prototype.x : any
>C.prototype : any
>C : typeof C
>prototype : any
>x : any
new C().x;
>new C().x : number
>new C() : C
>C : typeof C
>x : number
@@ -0,0 +1,9 @@
// @allowJs: true
// @checkJs: true
// @noEmit: true
// @Filename: /a.js
function C() { this.x = false; };
/** @type {number} */
C.prototype.x;
new C().x;
@@ -13,10 +13,12 @@
////whatever
// @Filename: /a.tsx
////declare namespace JSX {
//// interface Element {}
//// interface IntrinsicElements {
//// div: {};
////declare global {
//// namespace JSX {
//// interface Element {}
//// interface IntrinsicElements {
//// div: {};
//// }
//// }
////}
////const ctr = </*openTag*/;
@@ -9,7 +9,7 @@ const multiLineOffset = 12;
//// foo();
//// /*2*/foo(a);
//// /*3*/foo(a, b);
//// /*4*/ foo(a, {x: string}, [c]);
//// /*4*/foo(a, {x: string}, [c]);
//// /*5*/foo(a?, b?, ...args) {
//// }
////}
@@ -43,7 +43,8 @@ verify.docCommentTemplateAt("4", multiLineOffset,
* @param a
* @param param1
* @param param2
*/`);
*/
`);
verify.docCommentTemplateAt("5", multiLineOffset,
`/**
@@ -0,0 +1,12 @@
/// <reference path='fourslash.ts' />
/////*above*/
////const x = /*next*/ function f(p) {}
for (const marker of test.markerNames()) {
verify.docCommentTemplateAt(marker, 8,
`/**
*
* @param p
*/`);
}
@@ -12,8 +12,7 @@ const multiLineOffset = 12;
//// [1 + 2 + 3 + Math.rand()](x: number, y: string, z = true) { }
////}
verify.docCommentTemplateAt("0", singleLineOffset,
"/** */");
verify.docCommentTemplateAt("0", singleLineOffset, "/** */");
verify.docCommentTemplateAt("1", multiLineOffset,
`/**
@@ -21,4 +20,4 @@ verify.docCommentTemplateAt("1", multiLineOffset,
* @param x
* @param y
* @param z
*/`);
*/`);
@@ -1,6 +1,5 @@
/// <reference path='fourslash.ts' />
// @Filename: regex.ts
////var regex = /*0*///*1*/asdf/*2*/ /*3*///*4*/;
for (const marker of test.markers()) {
+5 -3
View File
@@ -139,6 +139,7 @@ declare namespace FourSlashInterface {
file(name: string, content?: string, scriptKindName?: string): any;
select(startMarker: string, endMarker: string): void;
selectRange(range: Range): void;
selectAllInFile(fileName: string): void;
}
class verifyNegatable {
private negative;
@@ -178,7 +179,7 @@ declare namespace FourSlashInterface {
isInCommentAtPosition(onlyMultiLineDiverges?: boolean): void;
codeFix(options: {
description: string,
newFileContent?: string | { readonly [fileName: string]: string },
newFileContent?: NewFileContent,
newRangeContent?: string,
errorCode?: number,
index?: number,
@@ -336,7 +337,7 @@ declare namespace FourSlashInterface {
getEditsForFileRename(options: {
oldPath: string;
newPath: string;
newFileContents: { [fileName: string]: string };
newFileContents: { readonly [fileName: string]: string };
}): void;
moveToNewFile(options: {
readonly newFileContents: { readonly [fileName: string]: string };
@@ -357,7 +358,7 @@ declare namespace FourSlashInterface {
enableFormatting(): void;
disableFormatting(): void;
applyRefactor(options: { refactorName: string, actionName: string, actionDescription: string, newContent: string }): void;
applyRefactor(options: { refactorName: string, actionName: string, actionDescription: string, newContent: NewFileContent }): void;
}
class debug {
printCurrentParameterHelp(): void;
@@ -573,6 +574,7 @@ declare namespace FourSlashInterface {
}
type ArrayOrSingle<T> = T | ReadonlyArray<T>;
type NewFileContent = string | { readonly [fileName: string]: string };
}
declare function verifyOperationIsCancelled(f: any): void;
declare var test: FourSlashInterface.test_;
@@ -14,6 +14,9 @@
////[|/*useI*/i|]();
////[|/*useJ*/j|]();
////const o = { m: /*m*/() => 0 };
////o.[|/*useM*/m|]();
verify.goToDefinition({
useF: "f",
useG: ["g", "f"],
@@ -21,4 +24,5 @@ verify.goToDefinition({
useI: "i",
useJ: ["j", "i"],
useM: "m",
});
@@ -0,0 +1,25 @@
/// <reference path='fourslash.ts' />
// @Filename: /foo.ts
////declare module "foo" {
//// /*a*/export default function foo(): void;/*b*/
////}
// @Filename: /b.ts
////import foo from "foo";
goTo.select("a", "b");
edit.applyRefactor({
refactorName: "Convert export",
actionName: "Convert default export to named export",
actionDescription: "Convert default export to named export",
newContent: {
"/foo.ts":
`declare module "foo" {
export function foo(): void;
}`,
"/b.ts":
`import { foo } from "foo";`,
},
});
@@ -0,0 +1,42 @@
/// <reference path='fourslash.ts' />
// @Filename: /a.ts
/////*a*/export default function f() {}/*b*/
// @Filename: /b.ts
////import f from "./a";
////import { default as f } from "./a";
////import { default as g } from "./a";
////import f, * as a from "./a";
////
////export { default } from "./a";
////export { default as f } from "./a";
////export { default as i } from "./a";
////
////import * as a from "./a";
////a.default();
goTo.select("a", "b");
edit.applyRefactor({
refactorName: "Convert export",
actionName: "Convert default export to named export",
actionDescription: "Convert default export to named export",
newContent: {
"/a.ts":
`export function f() {}`,
"/b.ts":
`import { f } from "./a";
import { f } from "./a";
import { f as g } from "./a";
import * as a from "./a";
import { f } from "./a";
export { f as default } from "./a";
export { f } from "./a";
export { f as i } from "./a";
import * as a from "./a";
a.f();`,
},
});
@@ -0,0 +1,79 @@
/// <reference path='fourslash.ts' />
// @Filename: /fn.ts
////export function f() {}
// @Filename: /cls.ts
////export class C {}
// @Filename: /interface.ts
////export interface I {}
// @Filename: /enum.ts
////export const enum E {}
// @Filename: /namespace.ts
////export namespace N {}
// @Filename: /type.ts
////export type T = number;
// @Filename: /var_unused.ts
////export const x = 0;
// @Filename: /var_unused_noInitializer.ts
////export const x;
// @Filename: /var_used.ts
////export const x = 0;
////x;
const tests: { [fileName: string]: string | undefined } = {
fn: `export default function f() {}`,
cls: `export default class C {}`,
interface: `export default interface I {}`,
enum:
`const enum E {}
export default E;
`,
namespace:
`namespace N {}
export default N;
`,
type:
`type T = number;
export default T;
`,
var_unused: `export default 0;`,
var_unused_noInitializer: undefined,
var_used:
`const x = 0;
export default x;
x;`,
};
for (const name in tests) {
const newContent = tests[name];
const fileName = `/${name}.ts`;
goTo.selectAllInFile(fileName);
if (newContent === undefined) {
verify.refactorsAvailable([]);
}
else {
edit.applyRefactor({
refactorName: "Convert export",
actionName: "Convert named export to default export",
actionDescription: "Convert named export to default export",
newContent: { [fileName]: newContent },
});
}
}
@@ -0,0 +1,39 @@
/// <reference path='fourslash.ts' />
// @Filename: /a.ts
/////*a*/export function f() {}/*b*/
// @Filename: /b.ts
////import { f } from "./a";
////import { f as g } from "./a";
////import { f, other } from "./a";
////
////export { f } from "./a";
////export { f as i } from "./a";
////export { f as default } from "./a";
////
////import * as a from "./a";
////a.f();
goTo.select("a", "b");
edit.applyRefactor({
refactorName: "Convert export",
actionName: "Convert named export to default export",
actionDescription: "Convert named export to default export",
newContent: {
"/a.ts":
`export default function f() {}`,
"/b.ts":
`import f from "./a";
import g from "./a";
import f, { other } from "./a";
export { default as f } from "./a";
export { default as i } from "./a";
export { default } from "./a";
import * as a from "./a";
a.default();`,
},
});
@@ -0,0 +1,8 @@
/// <reference path='fourslash.ts' />
// @Filename: /a.ts
/////*a*/export function f() {}/*b*/
////export default function g() {}
goTo.select("a", "b");
verify.refactorsAvailable([]);