mirror of
https://github.com/microsoft/TypeScript.git
synced 2025-11-18 17:21:48 +00:00
Merge branch 'master' into fix-missing-enum-member
This commit is contained in:
+2
-1
@@ -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
@@ -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
@@ -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" });
|
||||
}
|
||||
|
||||
Generated
+38
-79
@@ -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
@@ -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": {
|
||||
|
||||
@@ -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;
|
||||
@@ -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(); });
|
||||
@@ -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
@@ -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
@@ -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
@@ -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);
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
Vendored
+22
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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) {
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
@@ -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));
|
||||
|
||||
|
||||
@@ -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: () => {
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
Vendored
+1
-2
@@ -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" />
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
@@ -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 {
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
@@ -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:
|
||||
|
||||
@@ -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,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";
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {};
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,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
@@ -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()) {
|
||||
|
||||
@@ -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([]);
|
||||
Reference in New Issue
Block a user