diff --git a/.gitmodules b/.gitmodules
index ccb2be81520..fdf474a693d 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -29,3 +29,8 @@
[submodule "tests/cases/user/puppeteer/puppeteer"]
path = tests/cases/user/puppeteer/puppeteer
url = https://github.com/GoogleChrome/puppeteer.git
+ ignore = all
+[submodule "tests/cases/user/axios-src/axios-src"]
+ path = tests/cases/user/axios-src/axios-src
+ url = https://github.com/axios/axios.git
+ ignore = all
diff --git a/Gulpfile.ts b/Gulpfile.js
similarity index 87%
rename from Gulpfile.ts
rename to Gulpfile.js
index 7f7e525564c..afa7e775dcd 100644
--- a/Gulpfile.ts
+++ b/Gulpfile.js
@@ -1,35 +1,27 @@
///
-import * as cp from "child_process";
-import * as path from "path";
-import * as fs from "fs";
-import child_process = require("child_process");
-import originalGulp = require("gulp");
-import helpMaker = require("gulp-help");
-import runSequence = require("run-sequence");
-import concat = require("gulp-concat");
-import clone = require("gulp-clone");
-import newer = require("gulp-newer");
-import tsc = require("gulp-typescript");
-declare module "gulp-typescript" {
- interface Settings {
- pretty?: boolean;
- newLine?: string;
- noImplicitThis?: boolean;
- stripInternal?: boolean;
- types?: string[];
- }
-}
-import * as insert from "gulp-insert";
-import * as sourcemaps from "gulp-sourcemaps";
-import Q = require("q");
-import del = require("del");
-import mkdirP = require("mkdirp");
-import minimist = require("minimist");
-import browserify = require("browserify");
-import through2 = require("through2");
-import merge2 = require("merge2");
-import * as os from "os";
-import fold = require("travis-fold");
+// @ts-check
+const cp = require("child_process");
+const path = require("path");
+const fs = require("fs");
+const child_process = require("child_process");
+const originalGulp = require("gulp");
+const helpMaker = require("gulp-help");
+const runSequence = require("run-sequence");
+const concat = require("gulp-concat");
+const clone = require("gulp-clone");
+const newer = require("gulp-newer");
+const tsc = require("gulp-typescript");
+const insert = require("gulp-insert");
+const sourcemaps = require("gulp-sourcemaps");
+const Q = require("q");
+const del = require("del");
+const mkdirP = require("mkdirp");
+const minimist = require("minimist");
+const browserify = require("browserify");
+const through2 = require("through2");
+const merge2 = require("merge2");
+const os = require("os");
+const fold = require("travis-fold");
const gulp = helpMaker(originalGulp);
Error.stackTraceLimit = 1000;
@@ -73,17 +65,26 @@ const cmdLineOptions = minimist(process.argv.slice(2), {
});
const noop = () => {}; // tslint:disable-line no-empty
-function exec(cmd: string, args: string[], complete: () => void = noop, error: (e: any, status: number) => void = noop) {
+/**
+ * @param {string} cmd
+ * @param {string[]} args
+ * @param {() => void} complete
+ * @param {(e: *, status: number) => void} error
+ */
+function exec(cmd, args, complete = noop, error = noop) {
console.log(`${cmd} ${args.join(" ")}`);
// TODO (weswig): Update child_process types to add windowsVerbatimArguments to the type definition
const subshellFlag = isWin ? "/c" : "-c";
const command = isWin ? [possiblyQuote(cmd), ...args] : [`${cmd} ${args.join(" ")}`];
- const ex = cp.spawn(isWin ? "cmd" : "/bin/sh", [subshellFlag, ...command], { stdio: "inherit", windowsVerbatimArguments: true } as any);
+ const ex = cp.spawn(isWin ? "cmd" : "/bin/sh", [subshellFlag, ...command], { stdio: "inherit", windowsVerbatimArguments: true });
ex.on("exit", (code) => code === 0 ? complete() : error(/*e*/ undefined, code));
ex.on("error", error);
}
-function possiblyQuote(cmd: string) {
+/**
+ * @param {string} cmd
+ */
+function possiblyQuote(cmd) {
return cmd.indexOf(" ") >= 0 ? `"${cmd}"` : cmd;
}
@@ -220,7 +221,12 @@ const configurePreleleaseTs = path.join(scriptsDirectory, "configurePrerelease.t
const packageJson = "package.json";
const versionFile = path.join(compilerDirectory, "core.ts");
-function needsUpdate(source: string | string[], dest: string | string[]): boolean {
+/**
+ * @param {string | string[]} source
+ * @param {string | string[]} dest
+ * @returns {boolean}
+ */
+function needsUpdate(source, dest) {
if (typeof source === "string" && typeof dest === "string") {
if (fs.existsSync(dest)) {
const {mtime: outTime} = fs.statSync(dest);
@@ -283,8 +289,13 @@ function needsUpdate(source: string | string[], dest: string | string[]): boolea
return true;
}
-function getCompilerSettings(base: tsc.Settings, useBuiltCompiler?: boolean): tsc.Settings {
- const copy: tsc.Settings = {};
+/**
+ * @param {tsc.Settings} base
+ * @param {boolean=} useBuiltCompiler
+ * @returns {tsc.Settings}
+ */
+function getCompilerSettings(base, useBuiltCompiler) {
+ const copy = /** @type {tsc.Settings} */ ({});
for (const key in base) {
copy[key] = base[key];
}
@@ -293,16 +304,17 @@ function getCompilerSettings(base: tsc.Settings, useBuiltCompiler?: boolean): ts
}
copy.newLine = "lf";
if (useBuiltCompiler === true) {
- copy.typescript = require("./built/local/typescript.js");
+ copy.typescript = /** @type {*} */ (require("./built/local/typescript.js"));
}
else if (useBuiltCompiler === false) {
- copy.typescript = require("./lib/typescript.js");
+ copy.typescript = /** @type {*} */ (require("./lib/typescript.js"));
}
return copy;
}
gulp.task(configurePreleleaseJs, /*help*/ false, [], () => {
- const settings: tsc.Settings = {
+ /** @type {tsc.Settings} */
+ const settings = {
declaration: false,
removeComments: true,
noResolve: false,
@@ -332,7 +344,8 @@ const importDefinitelyTypedTestsJs = path.join(importDefinitelyTypedTestsDirecto
const importDefinitelyTypedTestsTs = path.join(importDefinitelyTypedTestsDirectory, "importDefinitelyTypedTests.ts");
gulp.task(importDefinitelyTypedTestsJs, /*help*/ false, [], () => {
- const settings: tsc.Settings = getCompilerSettings({
+ /** @type {tsc.Settings} */
+ const settings = getCompilerSettings({
declaration: false,
removeComments: true,
noResolve: false,
@@ -394,7 +407,8 @@ const generateLocalizedDiagnosticMessagesJs = path.join(scriptsDirectory, "gener
const generateLocalizedDiagnosticMessagesTs = path.join(scriptsDirectory, "generateLocalizedDiagnosticMessages.ts");
gulp.task(generateLocalizedDiagnosticMessagesJs, /*help*/ false, [], () => {
- const settings: tsc.Settings = getCompilerSettings({
+ /** @type {tsc.Settings} */
+ const settings = getCompilerSettings({
target: "es5",
declaration: false,
removeComments: true,
@@ -425,8 +439,12 @@ const nodePackageFile = path.join(builtLocalDirectory, "typescript.js");
const nodeDefinitionsFile = path.join(builtLocalDirectory, "typescript.d.ts");
const nodeStandaloneDefinitionsFile = path.join(builtLocalDirectory, "typescript_standalone.d.ts");
-let copyrightContent: string;
-function prependCopyright(outputCopyright: boolean = !useDebugMode) {
+/** @type {string} */
+let copyrightContent;
+/**
+ * @param {boolean} outputCopyright
+ */
+function prependCopyright(outputCopyright = !useDebugMode) {
return insert.prepend(outputCopyright ? (copyrightContent || (copyrightContent = fs.readFileSync(copyright).toString())) : "");
}
@@ -518,9 +536,10 @@ const tsserverLibraryDefinitionFile = path.join(builtLocalDirectory, "tsserverli
gulp.task(tsserverLibraryFile, /*help*/ false, [servicesFile, typesMapJson], (done) => {
const serverLibraryProject = tsc.createProject("src/server/tsconfig.library.json", getCompilerSettings({ removeComments: false }, /*useBuiltCompiler*/ true));
- const {js, dts}: { js: NodeJS.ReadableStream, dts: NodeJS.ReadableStream } = serverLibraryProject.src()
+ /** @type {{ js: NodeJS.ReadableStream, dts: NodeJS.ReadableStream }} */
+ const {js, dts} = serverLibraryProject.src()
.pipe(sourcemaps.init())
- .pipe(newer({ dest: tsserverLibraryFile, extra: ["src/compiler/**/*.ts", "src/services/**/*.ts"] }))
+ .pipe(newer(/** @type {*} */({ dest: tsserverLibraryFile, extra: ["src/compiler/**/*.ts", "src/services/**/*.ts"] })))
.pipe(serverLibraryProject());
return merge2([
@@ -555,7 +574,8 @@ const specWord = path.join(docDirectory, "TypeScript Language Specification.docx
const specMd = path.join(docDirectory, "spec.md");
gulp.task(word2mdJs, /*help*/ false, [], () => {
- const settings: tsc.Settings = getCompilerSettings({
+ /** @type {tsc.Settings} */
+ const settings = getCompilerSettings({
outFile: word2mdJs
}, /*useBuiltCompiler*/ false);
return gulp.src(word2mdTs)
@@ -634,7 +654,8 @@ function deleteTemporaryProjectOutput() {
return del(path.join(localBaseline, "projectOutput/"));
}
-let savedNodeEnv: string;
+/** @type {string} */
+let savedNodeEnv;
function setNodeEnvToDevelopment() {
savedNodeEnv = process.env.NODE_ENV;
process.env.NODE_ENV = "development";
@@ -644,7 +665,12 @@ function restoreSavedNodeEnv() {
process.env.NODE_ENV = savedNodeEnv;
}
-function runConsoleTests(defaultReporter: string, runInParallel: boolean, done: (e?: any) => void) {
+/**
+ * @param {string} defaultReporter
+ * @param {boolean} runInParallel
+ * @param {(e?: any) => void} done
+ */
+function runConsoleTests(defaultReporter, runInParallel, done) {
const lintFlag = cmdLineOptions.lint;
cleanTestDirs((err) => {
if (err) { console.error(err); failWithStatus(err, 1); }
@@ -719,7 +745,11 @@ function runConsoleTests(defaultReporter: string, runInParallel: boolean, done:
}
});
- function failWithStatus(err?: any, status?: number) {
+ /**
+ * @param {any=} err
+ * @param {number=} status
+ */
+ function failWithStatus(err, status) {
if (err || status) {
process.exit(typeof status === "number" ? status : 2);
}
@@ -735,7 +765,11 @@ function runConsoleTests(defaultReporter: string, runInParallel: boolean, done:
}
}
- function finish(error?: any, errorStatus?: number) {
+ /**
+ * @param {any=} error
+ * @param {number=} errorStatus
+ */
+ function finish(error, errorStatus) {
restoreSavedNodeEnv();
deleteTestConfig().then(deleteTemporaryProjectOutput).then(() => {
if (error !== undefined || errorStatus !== undefined) {
@@ -765,7 +799,8 @@ gulp.task("runtests",
const nodeServerOutFile = "tests/webTestServer.js";
const nodeServerInFile = "tests/webTestServer.ts";
gulp.task(nodeServerOutFile, /*help*/ false, [servicesFile], () => {
- const settings: tsc.Settings = getCompilerSettings({ module: "commonjs" }, /*useBuiltCompiler*/ true);
+ /** @type {tsc.Settings} */
+ const settings = getCompilerSettings({ module: "commonjs" }, /*useBuiltCompiler*/ true);
return gulp.src(nodeServerInFile)
.pipe(newer(nodeServerOutFile))
.pipe(sourcemaps.init())
@@ -774,16 +809,18 @@ gulp.task(nodeServerOutFile, /*help*/ false, [servicesFile], () => {
.pipe(gulp.dest(path.dirname(nodeServerOutFile)));
});
-import convertMap = require("convert-source-map");
-import sorcery = require("sorcery");
-import Vinyl = require("vinyl");
+const convertMap = require("convert-source-map");
+const sorcery = require("sorcery");
+const Vinyl = require("vinyl");
const bundlePath = path.resolve("built/local/bundle.js");
gulp.task("browserify", "Runs browserify on run.js to produce a file suitable for running tests in the browser", [servicesFile], (done) => {
const testProject = tsc.createProject("src/harness/tsconfig.json", getCompilerSettings({ outFile: bundlePath, inlineSourceMap: true }, /*useBuiltCompiler*/ true));
- let originalMap: any;
- let prebundledContent: string;
+ /** @type {*} */
+ let originalMap;
+ /** @type {string} */
+ let prebundledContent;
browserify(testProject.src()
.pipe(newer(bundlePath))
.pipe(sourcemaps.init())
@@ -847,8 +884,10 @@ gulp.task("browserify", "Runs browserify on run.js to produce a file suitable fo
});
});
-
-function cleanTestDirs(done: (e?: any) => void) {
+/**
+ * @param {(e?: any) => void} done
+ */
+function cleanTestDirs(done) {
// Clean the local baselines & Rwc baselines directories
del([
localBaseline,
@@ -864,8 +903,17 @@ function cleanTestDirs(done: (e?: any) => void) {
});
}
-// used to pass data from jake command line directly to run.js
-function writeTestConfigFile(tests: string, runners: string, light: boolean, taskConfigsFolder?: string, workerCount?: number, stackTraceLimit?: string, timeout?: number) {
+/**
+ * used to pass data from jake command line directly to run.js
+ * @param {string} tests
+ * @param {string} runners
+ * @param {boolean} light
+ * @param {string=} taskConfigsFolder
+ * @param {number=} workerCount
+ * @param {string=} stackTraceLimit
+ * @param {number=} timeout
+ */
+function writeTestConfigFile(tests, runners, light, taskConfigsFolder, workerCount, stackTraceLimit, timeout) {
const testConfigContents = JSON.stringify({
test: tests ? [tests] : undefined,
runner: runners ? runners.split(",") : undefined,
@@ -966,7 +1014,7 @@ gulp.task("baseline-accept-test262", "Makes the most recent test262 test results
const webhostPath = "tests/webhost/webtsc.ts";
const webhostJsPath = "tests/webhost/webtsc.js";
gulp.task(webhostJsPath, /*help*/ false, [servicesFile], () => {
- const settings: tsc.Settings = getCompilerSettings({
+ const settings = getCompilerSettings({
outFile: webhostJsPath
}, /*useBuiltCompiler*/ true);
return gulp.src(webhostPath)
@@ -986,7 +1034,7 @@ gulp.task("webhost", "Builds the tsc web host", [webhostJsPath], () => {
const perftscPath = "tests/perftsc.ts";
const perftscJsPath = "built/local/perftsc.js";
gulp.task(perftscJsPath, /*help*/ false, [servicesFile], () => {
- const settings: tsc.Settings = getCompilerSettings({
+ const settings = getCompilerSettings({
outFile: perftscJsPath
}, /*useBuiltCompiler*/ true);
return gulp.src(perftscPath)
@@ -1017,7 +1065,7 @@ gulp.task(loggedIOJsPath, /*help*/ false, [], (done) => {
const instrumenterPath = path.join(harnessDirectory, "instrumenter.ts");
const instrumenterJsPath = path.join(builtLocalDirectory, "instrumenter.js");
gulp.task(instrumenterJsPath, /*help*/ false, [servicesFile], () => {
- const settings: tsc.Settings = getCompilerSettings({
+ const settings = getCompilerSettings({
module: "commonjs",
target: "es5",
lib: [
@@ -1044,7 +1092,7 @@ gulp.task("update-sublime", "Updates the sublime plugin's tsserver", ["local", s
});
gulp.task("build-rules", "Compiles tslint rules to js", () => {
- const settings: tsc.Settings = getCompilerSettings({ module: "commonjs", lib: ["es6"] }, /*useBuiltCompiler*/ false);
+ const settings = getCompilerSettings({ module: "commonjs", lib: ["es6"] }, /*useBuiltCompiler*/ false);
const dest = path.join(builtLocalDirectory, "tslint");
return gulp.src("scripts/tslint/**/*.ts")
.pipe(newer({
@@ -1057,51 +1105,6 @@ gulp.task("build-rules", "Compiles tslint rules to js", () => {
.pipe(gulp.dest(dest));
});
-const lintTargets = [
- "Gulpfile.ts",
- "src/compiler/**/*.ts",
- "src/harness/**/*.ts",
- "!src/harness/unittests/services/formatting/**/*.ts",
- "src/server/**/*.ts",
- "scripts/tslint/**/*.ts",
- "src/services/**/*.ts",
- "tests/*.ts", "tests/webhost/*.ts" // Note: does *not* descend recursively
-];
-
-function sendNextFile(files: {path: string}[], child: cp.ChildProcess, callback: (failures: number) => void, failures: number) {
- const file = files.pop();
- if (file) {
- console.log(`Linting '${file.path}'.`);
- child.send({ kind: "file", name: file.path });
- }
- else {
- child.send({ kind: "close" });
- callback(failures);
- }
-}
-
-function spawnLintWorker(files: {path: string}[], callback: (failures: number) => void) {
- const child = cp.fork("./scripts/parallel-lint");
- let failures = 0;
- child.on("message", data => {
- switch (data.kind) {
- case "result":
- if (data.failures > 0) {
- failures += data.failures;
- console.log(data.output);
- }
- sendNextFile(files, child, callback, failures);
- break;
- case "error":
- console.error(data.error);
- failures++;
- sendNextFile(files, child, callback, failures);
- break;
- }
- });
- sendNextFile(files, child, callback, failures);
-}
-
gulp.task("lint", "Runs tslint on the compiler sources. Optional arguments are: --f[iles]=regex", ["build-rules"], () => {
if (fold.isTravis()) console.log(fold.start("lint"));
for (const project of ["scripts/tslint/tsconfig.json", "src/tsconfig-base.json"]) {
diff --git a/package.json b/package.json
index dff8abf71f5..452b4be72c3 100644
--- a/package.json
+++ b/package.json
@@ -78,7 +78,6 @@
"source-map-support": "latest",
"through2": "latest",
"travis-fold": "latest",
- "ts-node": "latest",
"tslint": "latest",
"vinyl": "latest",
"chalk": "latest",
diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts
index d009a956c43..bfc9c1fd550 100644
--- a/src/compiler/checker.ts
+++ b/src/compiler/checker.ts
@@ -1554,7 +1554,8 @@ namespace ts {
function isTypeParameterSymbolDeclaredInContainer(symbol: Symbol, container: Node) {
for (const decl of symbol.declarations) {
- if (decl.kind === SyntaxKind.TypeParameter && decl.parent === container) {
+ const parent = isJSDocTemplateTag(decl.parent) ? getJSDocHost(decl.parent) : decl.parent;
+ if (decl.kind === SyntaxKind.TypeParameter && parent === container) {
return true;
}
}
@@ -2060,10 +2061,10 @@ namespace ts {
let symbol: Symbol;
if (name.kind === SyntaxKind.Identifier) {
const message = meaning === namespaceMeaning ? Diagnostics.Cannot_find_namespace_0 : Diagnostics.Cannot_find_name_0;
-
- symbol = resolveName(location || name, name.escapedText, meaning, ignoreErrors ? undefined : message, name, /*isUse*/ true);
+ const symbolFromJSPrototype = isInJavaScriptFile(name) && resolveEntityNameFromJSPrototype(name, meaning);
+ symbol = resolveName(location || name, name.escapedText, meaning, ignoreErrors || symbolFromJSPrototype ? undefined : message, name, /*isUse*/ true);
if (!symbol) {
- return undefined;
+ return symbolFromJSPrototype;
}
}
else if (name.kind === SyntaxKind.QualifiedName || name.kind === SyntaxKind.PropertyAccessExpression) {
@@ -2114,6 +2115,18 @@ namespace ts {
return (symbol.flags & meaning) || dontResolveAlias ? symbol : resolveAlias(symbol);
}
+ function resolveEntityNameFromJSPrototype(name: Identifier, meaning: SymbolFlags) {
+ if (isJSDocTypeReference(name.parent) && isJSDocTag(name.parent.parent.parent)) {
+ const host = getJSDocHost(name.parent.parent.parent as JSDocTag);
+ if (isExpressionStatement(host) &&
+ isBinaryExpression(host.expression) &&
+ getSpecialPropertyAssignmentKind(host.expression) === SpecialPropertyAssignmentKind.PrototypeProperty) {
+ const secondaryLocation = getSymbolOfNode(host.expression.left).parent.valueDeclaration;
+ return resolveName(secondaryLocation, name.escapedText, meaning, /*nameNotFoundMessage*/ undefined, name, /*isUse*/ true);
+ }
+ }
+ }
+
function resolveExternalModuleName(location: Node, moduleReferenceExpression: Expression): Symbol {
return resolveExternalModuleNameWorker(location, moduleReferenceExpression, Diagnostics.Cannot_find_module_0);
}
@@ -4897,8 +4910,7 @@ namespace ts {
// in-place and returns the same array.
function appendTypeParameters(typeParameters: TypeParameter[], declarations: ReadonlyArray): TypeParameter[] {
for (const declaration of declarations) {
- const tp = getDeclaredTypeOfTypeParameter(getSymbolOfNode(declaration));
- typeParameters = appendIfUnique(typeParameters, tp);
+ typeParameters = appendIfUnique(typeParameters, getDeclaredTypeOfTypeParameter(getSymbolOfNode(declaration)));
}
return typeParameters;
}
@@ -4958,8 +4970,9 @@ namespace ts {
if (node.kind === SyntaxKind.InterfaceDeclaration || node.kind === SyntaxKind.ClassDeclaration ||
node.kind === SyntaxKind.ClassExpression || node.kind === SyntaxKind.TypeAliasDeclaration) {
const declaration = node;
- if (declaration.typeParameters) {
- result = appendTypeParameters(result, declaration.typeParameters);
+ const typeParameters = getEffectiveTypeParameterDeclarations(declaration);
+ if (typeParameters) {
+ result = appendTypeParameters(result, typeParameters);
}
}
}
@@ -5455,9 +5468,10 @@ namespace ts {
*/
function isThislessFunctionLikeDeclaration(node: FunctionLikeDeclaration): boolean {
const returnType = getEffectiveReturnTypeNode(node);
+ const typeParameters = getEffectiveTypeParameterDeclarations(node);
return (node.kind === SyntaxKind.Constructor || (returnType && isThislessType(returnType))) &&
node.parameters.every(isThislessVariableLikeDeclaration) &&
- (!node.typeParameters || node.typeParameters.every(isThislessTypeParameter));
+ (!typeParameters || typeParameters.every(isThislessTypeParameter));
}
/**
@@ -6735,8 +6749,7 @@ namespace ts {
function getTypeParametersFromDeclaration(declaration: DeclarationWithTypeParameters): TypeParameter[] {
let result: TypeParameter[];
forEach(getEffectiveTypeParameterDeclarations(declaration), node => {
- const tp = getDeclaredTypeOfTypeParameter(node.symbol);
- result = appendIfUnique(result, tp);
+ result = appendIfUnique(result, getDeclaredTypeOfTypeParameter(node.symbol));
});
return result;
}
@@ -7547,7 +7560,7 @@ namespace ts {
return constraints ? getSubstitutionType(typeVariable, getIntersectionType(append(constraints, typeVariable))) : typeVariable;
}
- function isJSDocTypeReference(node: NodeWithTypeArguments): node is TypeReferenceNode {
+ function isJSDocTypeReference(node: Node): node is TypeReferenceNode {
return node.flags & NodeFlags.JSDoc && node.kind === SyntaxKind.TypeReference;
}
@@ -9170,10 +9183,15 @@ namespace ts {
// aren't the right hand side of a generic type alias declaration we optimize by reducing the
// set of type parameters to those that are possibly referenced in the literal.
const declaration = symbol.declarations[0];
- const outerTypeParameters = getOuterTypeParameters(declaration, /*includeThisTypes*/ true) || emptyArray;
+ let outerTypeParameters = getOuterTypeParameters(declaration, /*includeThisTypes*/ true);
+ if (isJavaScriptConstructor(declaration)) {
+ const templateTagParameters = getTypeParametersFromDeclaration(declaration as DeclarationWithTypeParameters);
+ outerTypeParameters = addRange(outerTypeParameters, templateTagParameters);
+ }
+ typeParameters = outerTypeParameters || emptyArray;
typeParameters = symbol.flags & SymbolFlags.TypeLiteral && !target.aliasTypeArguments ?
- filter(outerTypeParameters, tp => isTypeParameterPossiblyReferenced(tp, declaration)) :
- outerTypeParameters;
+ filter(typeParameters, tp => isTypeParameterPossiblyReferenced(tp, declaration)) :
+ typeParameters;
links.outerTypeParameters = typeParameters;
if (typeParameters.length) {
links.instantiations = createMap();
@@ -17749,11 +17767,11 @@ namespace ts {
let typeArguments: NodeArray;
- if (!isTaggedTemplate && !isDecorator && !isJsxOpeningOrSelfClosingElement) {
+ if (!isDecorator && !isJsxOpeningOrSelfClosingElement) {
typeArguments = (node).typeArguments;
// We already perform checking on the type arguments on the class declaration itself.
- if ((node).expression.kind !== SyntaxKind.SuperKeyword) {
+ if (isTaggedTemplate || (node).expression.kind !== SyntaxKind.SuperKeyword) {
forEach(typeArguments, checkSourceElement);
}
}
@@ -17866,7 +17884,7 @@ namespace ts {
checkApplicableSignature(node, args, candidateForArgumentError, assignableRelation, /*excludeArgument*/ undefined, /*reportErrors*/ true);
}
else if (candidateForTypeArgumentError) {
- checkTypeArguments(candidateForTypeArgumentError, (node as CallExpression).typeArguments, /*reportErrors*/ true, fallbackError);
+ checkTypeArguments(candidateForTypeArgumentError, (node as CallExpression | TaggedTemplateExpression).typeArguments, /*reportErrors*/ true, fallbackError);
}
else if (typeArguments && every(signatures, sig => length(sig.typeParameters) !== typeArguments.length)) {
diagnostics.add(getTypeArgumentArityError(node, signatures, typeArguments));
@@ -18533,7 +18551,7 @@ namespace ts {
}
const type = funcSymbol && getJavaScriptClassType(funcSymbol);
if (type) {
- return type;
+ return signature.target ? instantiateType(type, signature.mapper) : type;
}
if (noImplicitAny) {
error(node, Diagnostics.new_expression_whose_target_lacks_a_construct_signature_implicitly_has_an_any_type);
@@ -18660,6 +18678,7 @@ namespace ts {
}
function checkTaggedTemplateExpression(node: TaggedTemplateExpression): Type {
+ checkGrammarTypeArguments(node, node.typeArguments);
if (languageVersion < ScriptTarget.ES2015) {
checkExternalEmitHelpers(node, ExternalEmitHelpers.MakeTemplateObject);
}
@@ -21865,6 +21884,11 @@ namespace ts {
// If the node had `@property` tags, `typeExpression` would have been set to the first property tag.
error(node.name, Diagnostics.JSDoc_typedef_tag_should_either_have_a_type_annotation_or_be_followed_by_property_or_member_tags);
}
+
+ if (node.name) {
+ checkTypeNameIsReserved(node.name, Diagnostics.Type_alias_name_cannot_be_0);
+ }
+ checkSourceElement(node.typeExpression);
}
function checkJSDocParameterTag(node: JSDocParameterTag) {
@@ -22155,8 +22179,9 @@ namespace ts {
): void {
// Only report errors on the last declaration for the type parameter container;
// this ensures that all uses have been accounted for.
- if (!(node.flags & NodeFlags.Ambient) && node.typeParameters && last(getSymbolOfNode(node)!.declarations) === node) {
- for (const typeParameter of node.typeParameters) {
+ const typeParameters = getEffectiveTypeParameterDeclarations(node);
+ if (!(node.flags & NodeFlags.Ambient) && typeParameters && last(getSymbolOfNode(node)!.declarations) === node) {
+ for (const typeParameter of typeParameters) {
if (!(getMergedSymbol(typeParameter.symbol).isReferenced & SymbolFlags.TypeParameter) && !isIdentifierThatStartsWithUnderScore(typeParameter.name)) {
addDiagnostic(UnusedKind.Parameter, createDiagnosticForNode(typeParameter.name, Diagnostics._0_is_declared_but_its_value_is_never_read, symbolName(typeParameter.symbol)));
}
@@ -23530,20 +23555,21 @@ namespace ts {
}
}
- function areTypeParametersIdentical(declarations: ReadonlyArray, typeParameters: TypeParameter[]) {
- const maxTypeArgumentCount = length(typeParameters);
- const minTypeArgumentCount = getMinTypeArgumentCount(typeParameters);
+ function areTypeParametersIdentical(declarations: ReadonlyArray, targetParameters: TypeParameter[]) {
+ const maxTypeArgumentCount = length(targetParameters);
+ const minTypeArgumentCount = getMinTypeArgumentCount(targetParameters);
for (const declaration of declarations) {
// If this declaration has too few or too many type parameters, we report an error
- const numTypeParameters = length(declaration.typeParameters);
+ const sourceParameters = getEffectiveTypeParameterDeclarations(declaration);
+ const numTypeParameters = length(sourceParameters);
if (numTypeParameters < minTypeArgumentCount || numTypeParameters > maxTypeArgumentCount) {
return false;
}
for (let i = 0; i < numTypeParameters; i++) {
- const source = declaration.typeParameters[i];
- const target = typeParameters[i];
+ const source = sourceParameters[i];
+ const target = targetParameters[i];
// If the type parameter node does not have the same as the resolved type
// parameter at this position, we report an error.
@@ -23604,7 +23630,7 @@ namespace ts {
checkCollisionWithRequireExportsInGeneratedCode(node, node.name);
checkCollisionWithGlobalPromiseInGeneratedCode(node, node.name);
}
- checkTypeParameters(node.typeParameters);
+ checkTypeParameters(getEffectiveTypeParameterDeclarations(node));
checkExportsOnMergedDeclarations(node);
const symbol = getSymbolOfNode(node);
const type = getDeclaredTypeOfSymbol(symbol);
@@ -24465,7 +24491,10 @@ namespace ts {
checkImportBinding(importClause.namedBindings);
}
else {
- forEach(importClause.namedBindings.elements, checkImportBinding);
+ const moduleExisted = resolveExternalModuleName(node, node.moduleSpecifier);
+ if (moduleExisted) {
+ forEach(importClause.namedBindings.elements, checkImportBinding);
+ }
}
}
}
@@ -24762,6 +24791,7 @@ namespace ts {
case SyntaxKind.JSDocNullableType:
case SyntaxKind.JSDocAllType:
case SyntaxKind.JSDocUnknownType:
+ case SyntaxKind.JSDocTypeLiteral:
checkJSDocTypeIsInJsFile(node);
forEachChild(node, checkSourceElement);
return;
@@ -26836,7 +26866,7 @@ namespace ts {
function checkGrammarClassLikeDeclaration(node: ClassLikeDeclaration): boolean {
const file = getSourceFileOfNode(node);
- return checkGrammarClassDeclarationHeritageClauses(node) || checkGrammarTypeParameterList(node.typeParameters, file);
+ return checkGrammarClassDeclarationHeritageClauses(node) || checkGrammarTypeParameterList(getEffectiveTypeParameterDeclarations(node), file);
}
function checkGrammarArrowFunction(node: Node, file: SourceFile): boolean {
diff --git a/src/compiler/core.ts b/src/compiler/core.ts
index 13140dac6ce..a615c73ec40 100644
--- a/src/compiler/core.ts
+++ b/src/compiler/core.ts
@@ -2987,18 +2987,19 @@ namespace ts {
}
/** Remove the *first* occurrence of `item` from the array. */
- export function unorderedRemoveItem(array: T[], item: T): void {
- unorderedRemoveFirstItemWhere(array, element => element === item);
+ export function unorderedRemoveItem(array: T[], item: T) {
+ return unorderedRemoveFirstItemWhere(array, element => element === item);
}
/** Remove the *first* element satisfying `predicate`. */
- function unorderedRemoveFirstItemWhere(array: T[], predicate: (element: T) => boolean): void {
+ function unorderedRemoveFirstItemWhere(array: T[], predicate: (element: T) => boolean) {
for (let i = 0; i < array.length; i++) {
if (predicate(array[i])) {
unorderedRemoveItemAt(array, i);
- break;
+ return true;
}
}
+ return false;
}
export type GetCanonicalFileName = (fileName: string) => string;
diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json
index 235788258af..a5119f7feb7 100644
--- a/src/compiler/diagnosticMessages.json
+++ b/src/compiler/diagnosticMessages.json
@@ -3914,7 +3914,7 @@
"category": "Message",
"code": 90007
},
- "Add 'this.' to unresolved variable": {
+ "Add '{0}.' to unresolved variable": {
"category": "Message",
"code": 90008
},
@@ -4122,7 +4122,7 @@
"category": "Message",
"code": 95036
},
- "Add 'this.' to all unresolved variables matching a member name": {
+ "Add qualifier to all unresolved variables matching a member name": {
"category": "Message",
"code": 95037
},
diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts
index 430aa8aceb5..5b87dce92dd 100644
--- a/src/compiler/emitter.ts
+++ b/src/compiler/emitter.ts
@@ -1463,6 +1463,7 @@ namespace ts {
function emitTaggedTemplateExpression(node: TaggedTemplateExpression) {
emitExpression(node.tag);
+ emitTypeArguments(node, node.typeArguments);
writeSpace();
emitExpression(node.template);
}
diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts
index 765c3c9cb4a..e3191930c0e 100644
--- a/src/compiler/factory.ts
+++ b/src/compiler/factory.ts
@@ -1032,17 +1032,32 @@ namespace ts {
: node;
}
- export function createTaggedTemplate(tag: Expression, template: TemplateLiteral) {
+ export function createTaggedTemplate(tag: Expression, template: TemplateLiteral): TaggedTemplateExpression;
+ export function createTaggedTemplate(tag: Expression, typeArguments: ReadonlyArray, template: TemplateLiteral): TaggedTemplateExpression;
+ /** @internal */
+ export function createTaggedTemplate(tag: Expression, typeArgumentsOrTemplate: ReadonlyArray | TemplateLiteral, template?: TemplateLiteral): TaggedTemplateExpression;
+ export function createTaggedTemplate(tag: Expression, typeArgumentsOrTemplate: ReadonlyArray | TemplateLiteral, template?: TemplateLiteral) {
const node = createSynthesizedNode(SyntaxKind.TaggedTemplateExpression);
node.tag = parenthesizeForAccess(tag);
- node.template = template;
+ if (template) {
+ node.typeArguments = asNodeArray(typeArgumentsOrTemplate as ReadonlyArray);
+ node.template = template!;
+ }
+ else {
+ node.typeArguments = undefined;
+ node.template = typeArgumentsOrTemplate as TemplateLiteral;
+ }
return node;
}
- export function updateTaggedTemplate(node: TaggedTemplateExpression, tag: Expression, template: TemplateLiteral) {
+ export function updateTaggedTemplate(node: TaggedTemplateExpression, tag: Expression, template: TemplateLiteral): TaggedTemplateExpression;
+ export function updateTaggedTemplate(node: TaggedTemplateExpression, tag: Expression, typeArguments: ReadonlyArray, template: TemplateLiteral): TaggedTemplateExpression;
+ export function updateTaggedTemplate(node: TaggedTemplateExpression, tag: Expression, typeArgumentsOrTemplate: ReadonlyArray | TemplateLiteral, template?: TemplateLiteral) {
return node.tag !== tag
- || node.template !== template
- ? updateNode(createTaggedTemplate(tag, template), node)
+ || (template
+ ? node.typeArguments !== typeArgumentsOrTemplate || node.template !== template
+ : node.typeArguments !== undefined || node.template !== typeArgumentsOrTemplate)
+ ? updateNode(createTaggedTemplate(tag, typeArgumentsOrTemplate, template), node)
: node;
}
diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts
index 74d54ed28fb..4cf4c6e4c67 100644
--- a/src/compiler/parser.ts
+++ b/src/compiler/parser.ts
@@ -223,6 +223,7 @@ namespace ts {
visitNodes(cbNode, cbNodes, (node).arguments);
case SyntaxKind.TaggedTemplateExpression:
return visitNode(cbNode, (node).tag) ||
+ visitNodes(cbNode, cbNodes, (node).typeArguments) ||
visitNode(cbNode, (node).template);
case SyntaxKind.TypeAssertionExpression:
return visitNode(cbNode, (node).type) ||
@@ -4362,13 +4363,8 @@ namespace ts {
continue;
}
- if (token() === SyntaxKind.NoSubstitutionTemplateLiteral || token() === SyntaxKind.TemplateHead) {
- const tagExpression = createNode(SyntaxKind.TaggedTemplateExpression, expression.pos);
- tagExpression.tag = expression;
- tagExpression.template = token() === SyntaxKind.NoSubstitutionTemplateLiteral
- ? parseLiteralNode()
- : parseTemplateExpression();
- expression = finishNode(tagExpression);
+ if (isTemplateStartOfTaggedTemplate()) {
+ expression = parseTaggedTemplateRest(expression, /*typeArguments*/ undefined);
continue;
}
@@ -4376,6 +4372,20 @@ namespace ts {
}
}
+ function isTemplateStartOfTaggedTemplate() {
+ return token() === SyntaxKind.NoSubstitutionTemplateLiteral || token() === SyntaxKind.TemplateHead;
+ }
+
+ function parseTaggedTemplateRest(tag: LeftHandSideExpression, typeArguments: NodeArray | undefined) {
+ const tagExpression = createNode(SyntaxKind.TaggedTemplateExpression, tag.pos);
+ tagExpression.tag = tag;
+ tagExpression.typeArguments = typeArguments;
+ tagExpression.template = token() === SyntaxKind.NoSubstitutionTemplateLiteral
+ ? parseLiteralNode()
+ : parseTemplateExpression();
+ return finishNode(tagExpression);
+ }
+
function parseCallExpressionRest(expression: LeftHandSideExpression): LeftHandSideExpression {
while (true) {
expression = parseMemberExpressionRest(expression);
@@ -4389,6 +4399,11 @@ namespace ts {
return expression;
}
+ if (isTemplateStartOfTaggedTemplate()) {
+ expression = parseTaggedTemplateRest(expression, typeArguments);
+ continue;
+ }
+
const callExpr = createNode(SyntaxKind.CallExpression, expression.pos);
callExpr.expression = expression;
callExpr.typeArguments = typeArguments;
@@ -4436,8 +4451,10 @@ namespace ts {
function canFollowTypeArgumentsInExpression(): boolean {
switch (token()) {
case SyntaxKind.OpenParenToken: // foo(
- // this case are the only case where this token can legally follow a type argument
- // list. So we definitely want to treat this as a type arg list.
+ case SyntaxKind.NoSubstitutionTemplateLiteral: // foo `...`
+ case SyntaxKind.TemplateHead: // foo `...${100}...`
+ // these are the only tokens can legally follow a type argument
+ // list. So we definitely want to treat them as type arg lists.
case SyntaxKind.DotToken: // foo.
case SyntaxKind.CloseParenToken: // foo)
@@ -4666,9 +4683,23 @@ namespace ts {
return finishNode(node);
}
+ let expression: MemberExpression = parsePrimaryExpression();
+ let typeArguments;
+ while (true) {
+ expression = parseMemberExpressionRest(expression);
+ typeArguments = tryParse(parseTypeArgumentsInExpression);
+ if (isTemplateStartOfTaggedTemplate()) {
+ Debug.assert(!!typeArguments,
+ "Expected a type argument list; all plain tagged template starts should be consumed in 'parseMemberExpressionRest'");
+ expression = parseTaggedTemplateRest(expression, typeArguments);
+ typeArguments = undefined;
+ }
+ break;
+ }
+
const node = createNode(SyntaxKind.NewExpression, fullStart);
- node.expression = parseMemberExpressionOrHigher();
- node.typeArguments = tryParse(parseTypeArgumentsInExpression);
+ node.expression = expression;
+ node.typeArguments = typeArguments;
if (node.typeArguments || token() === SyntaxKind.OpenParenToken) {
node.arguments = parseArgumentList();
}
diff --git a/src/compiler/program.ts b/src/compiler/program.ts
index 514397debc1..9eb82661cf5 100755
--- a/src/compiler/program.ts
+++ b/src/compiler/program.ts
@@ -1624,6 +1624,9 @@ namespace ts {
collectDynamicImportOrRequireCalls(node);
}
}
+ if ((file.flags & NodeFlags.PossiblyContainsDynamicImport) || isJavaScriptFile) {
+ collectDynamicImportOrRequireCalls(file.endOfFileToken);
+ }
file.imports = imports || emptyArray;
file.moduleAugmentations = moduleAugmentations || emptyArray;
@@ -2004,7 +2007,8 @@ namespace ts {
&& !options.noResolve
&& i < file.imports.length
&& !elideImport
- && !(isJsFile && !options.allowJs);
+ && !(isJsFile && !options.allowJs)
+ && (isInJavaScriptFile(file.imports[i]) || !(file.imports[i].flags & NodeFlags.JSDoc));
if (elideImport) {
modulesWithElidedImports.set(file.path, true);
diff --git a/src/compiler/resolutionCache.ts b/src/compiler/resolutionCache.ts
index c2b96649cf2..35e056546c0 100644
--- a/src/compiler/resolutionCache.ts
+++ b/src/compiler/resolutionCache.ts
@@ -10,6 +10,7 @@ namespace ts {
invalidateResolutionOfFile(filePath: Path): void;
removeResolutionsOfFile(filePath: Path): void;
+ setFilesWithInvalidatedNonRelativeUnresolvedImports(filesWithUnresolvedImports: Map>): void;
createHasInvalidatedResolution(forceAllFilesAsInvalidated?: boolean): HasInvalidatedResolution;
startCachingPerDirectoryResolution(): void;
@@ -74,6 +75,7 @@ namespace ts {
export function createResolutionCache(resolutionHost: ResolutionCacheHost, rootDirForResolution: string, logChangesWhenResolvingModule: boolean): ResolutionCache {
let filesWithChangedSetOfUnresolvedImports: Path[] | undefined;
let filesWithInvalidatedResolutions: Map | undefined;
+ let filesWithInvalidatedNonRelativeUnresolvedImports: Map> | undefined;
let allFilesHaveInvalidatedResolution = false;
const getCurrentDirectory = memoize(() => resolutionHost.getCurrentDirectory());
@@ -122,6 +124,7 @@ namespace ts {
resolveTypeReferenceDirectives,
removeResolutionsOfFile,
invalidateResolutionOfFile,
+ setFilesWithInvalidatedNonRelativeUnresolvedImports,
createHasInvalidatedResolution,
updateTypeRootsWatch,
closeTypeRootsWatch,
@@ -165,6 +168,16 @@ namespace ts {
return collected;
}
+ function isFileWithInvalidatedNonRelativeUnresolvedImports(path: Path) {
+ if (!filesWithInvalidatedNonRelativeUnresolvedImports) {
+ return false;
+ }
+
+ // Invalidated if file has unresolved imports
+ const value = filesWithInvalidatedNonRelativeUnresolvedImports.get(path);
+ return value && !!value.length;
+ }
+
function createHasInvalidatedResolution(forceAllFilesAsInvalidated?: boolean): HasInvalidatedResolution {
if (allFilesHaveInvalidatedResolution || forceAllFilesAsInvalidated) {
// Any file asked would have invalidated resolution
@@ -173,7 +186,8 @@ namespace ts {
}
const collected = filesWithInvalidatedResolutions;
filesWithInvalidatedResolutions = undefined;
- return path => collected && collected.has(path);
+ return path => (collected && collected.has(path)) ||
+ isFileWithInvalidatedNonRelativeUnresolvedImports(path);
}
function clearPerDirectoryResolutions() {
@@ -184,6 +198,7 @@ namespace ts {
function finishCachingPerDirectoryResolution() {
allFilesHaveInvalidatedResolution = false;
+ filesWithInvalidatedNonRelativeUnresolvedImports = undefined;
directoryWatchesOfFailedLookups.forEach((watcher, path) => {
if (watcher.refCount === 0) {
directoryWatchesOfFailedLookups.delete(path);
@@ -237,13 +252,15 @@ namespace ts {
const resolvedModules: R[] = [];
const compilerOptions = resolutionHost.getCompilationSettings();
-
+ const hasInvalidatedNonRelativeUnresolvedImport = logChanges && isFileWithInvalidatedNonRelativeUnresolvedImports(path);
const seenNamesInFile = createMap();
for (const name of names) {
let resolution = resolutionsInFile.get(name);
// Resolution is valid if it is present and not invalidated
if (!seenNamesInFile.has(name) &&
- allFilesHaveInvalidatedResolution || !resolution || resolution.isInvalidated) {
+ allFilesHaveInvalidatedResolution || !resolution || resolution.isInvalidated ||
+ // If the name is unresolved import that was invalidated, recalculate
+ (hasInvalidatedNonRelativeUnresolvedImport && !isExternalModuleNameRelative(name) && !getResolutionWithResolvedFileName(resolution))) {
const existingResolution = resolution;
const resolutionInDirectory = perDirectoryResolution.get(name);
if (resolutionInDirectory) {
@@ -284,7 +301,7 @@ namespace ts {
if (oldResolution === newResolution) {
return true;
}
- if (!oldResolution || !newResolution || oldResolution.isInvalidated) {
+ if (!oldResolution || !newResolution) {
return false;
}
const oldResult = getResolutionWithResolvedFileName(oldResolution);
@@ -577,6 +594,11 @@ namespace ts {
);
}
+ function setFilesWithInvalidatedNonRelativeUnresolvedImports(filesMap: Map>) {
+ Debug.assert(filesWithInvalidatedNonRelativeUnresolvedImports === filesMap || filesWithInvalidatedNonRelativeUnresolvedImports === undefined);
+ filesWithInvalidatedNonRelativeUnresolvedImports = filesMap;
+ }
+
function invalidateResolutionOfFailedLookupLocation(fileOrDirectoryPath: Path, isCreatingWatchedDirectory: boolean) {
let isChangedFailedLookupLocation: (location: string) => boolean;
if (isCreatingWatchedDirectory) {
diff --git a/src/compiler/sys.ts b/src/compiler/sys.ts
index c37315a2e56..be95e06eb46 100644
--- a/src/compiler/sys.ts
+++ b/src/compiler/sys.ts
@@ -428,6 +428,7 @@ namespace ts {
newLine: string;
useCaseSensitiveFileNames: boolean;
write(s: string): void;
+ writeOutputIsTTY?(): boolean;
readFile(path: string, encoding?: string): string | undefined;
getFileSize?(path: string): number;
writeFile(path: string, data: string, writeByteOrderMark?: boolean): void;
@@ -561,6 +562,9 @@ namespace ts {
write(s: string): void {
process.stdout.write(s);
},
+ writeOutputIsTTY() {
+ return process.stdout.isTTY;
+ },
readFile,
writeFile,
watchFile: getWatchFile(),
diff --git a/src/compiler/transformers/ts.ts b/src/compiler/transformers/ts.ts
index 6cb4be16c74..f29829d4607 100644
--- a/src/compiler/transformers/ts.ts
+++ b/src/compiler/transformers/ts.ts
@@ -502,6 +502,9 @@ namespace ts {
case SyntaxKind.NewExpression:
return visitNewExpression(node);
+ case SyntaxKind.TaggedTemplateExpression:
+ return visitTaggedTemplateExpression(node);
+
case SyntaxKind.NonNullExpression:
// TypeScript non-null expressions are removed, but their subtrees are preserved.
return visitNonNullExpression(node);
@@ -2547,6 +2550,14 @@ namespace ts {
visitNodes(node.arguments, visitor, isExpression));
}
+ function visitTaggedTemplateExpression(node: TaggedTemplateExpression) {
+ return updateTaggedTemplate(
+ node,
+ visitNode(node.tag, visitor, isExpression),
+ /*typeArguments*/ undefined,
+ visitNode(node.template, visitor, isExpression));
+ }
+
/**
* Determines whether to emit an enum declaration.
*
diff --git a/src/compiler/tsc.ts b/src/compiler/tsc.ts
index f16ca98c93d..3c73eba86ef 100644
--- a/src/compiler/tsc.ts
+++ b/src/compiler/tsc.ts
@@ -19,11 +19,18 @@ namespace ts {
let reportDiagnostic = createDiagnosticReporter(sys);
function updateReportDiagnostic(options: CompilerOptions) {
- if (options.pretty) {
+ if (shouldBePretty(options)) {
reportDiagnostic = createDiagnosticReporter(sys, /*pretty*/ true);
}
}
+ function shouldBePretty(options: CompilerOptions) {
+ if (typeof options.pretty === "undefined") {
+ return !!sys.writeOutputIsTTY && sys.writeOutputIsTTY();
+ }
+ return options.pretty;
+ }
+
function padLeft(s: string, length: number) {
while (s.length < length) {
s = " " + s;
@@ -159,7 +166,7 @@ namespace ts {
}
function createWatchStatusReporter(options: CompilerOptions) {
- return ts.createWatchStatusReporter(sys, !!options.pretty);
+ return ts.createWatchStatusReporter(sys, shouldBePretty(options));
}
function createWatchOfConfigFile(configParseResult: ParsedCommandLine, optionsToExtend: CompilerOptions) {
diff --git a/src/compiler/types.ts b/src/compiler/types.ts
index 63720d55900..4ea4b7ab8cc 100644
--- a/src/compiler/types.ts
+++ b/src/compiler/types.ts
@@ -1727,6 +1727,7 @@ namespace ts {
export interface TaggedTemplateExpression extends MemberExpression {
kind: SyntaxKind.TaggedTemplateExpression;
tag: LeftHandSideExpression;
+ typeArguments?: NodeArray;
template: TemplateLiteral;
}
@@ -1892,7 +1893,7 @@ namespace ts {
kind: SyntaxKind.DebuggerStatement;
}
- export interface MissingDeclaration extends DeclarationStatement, ClassElement, ObjectLiteralElement, TypeElement {
+ export interface MissingDeclaration extends DeclarationStatement {
kind: SyntaxKind.MissingDeclaration;
name?: Identifier;
}
@@ -3193,7 +3194,8 @@ namespace ts {
export type AnyValidImportOrReExport =
| (ImportDeclaration | ExportDeclaration) & { moduleSpecifier: StringLiteral }
| ImportEqualsDeclaration & { moduleReference: ExternalModuleReference & { expression: StringLiteral } }
- | RequireOrImportCall;
+ | RequireOrImportCall
+ | ImportTypeNode & { argument: LiteralType };
/* @internal */
export type RequireOrImportCall = CallExpression & { arguments: [StringLiteralLike] };
@@ -4194,7 +4196,7 @@ namespace ts {
preserveSymlinks?: boolean;
/* @internal */ preserveWatchOutput?: boolean;
project?: string;
- /* @internal */ pretty?: DiagnosticStyle;
+ /* @internal */ pretty?: boolean;
reactNamespace?: string;
jsxFactory?: string;
removeComments?: boolean;
@@ -4292,12 +4294,6 @@ namespace ts {
JSX,
}
- /* @internal */
- export const enum DiagnosticStyle {
- Simple,
- Pretty,
- }
-
/** Either a parsed command line or a parsed tsconfig.json */
export interface ParsedCommandLine {
options: CompilerOptions;
diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts
index f975e27dca3..730c3fc51d4 100644
--- a/src/compiler/utilities.ts
+++ b/src/compiler/utilities.ts
@@ -1710,8 +1710,10 @@ namespace ts {
return (node.parent as ExternalModuleReference).parent as AnyValidImportOrReExport;
case SyntaxKind.CallExpression:
return node.parent as AnyValidImportOrReExport;
+ case SyntaxKind.LiteralType:
+ return cast(node.parent.parent, isImportTypeNode) as ImportTypeNode & { argument: LiteralType };
default:
- return Debug.fail(Debug.showSyntaxKind(node));
+ return Debug.fail(Debug.showSyntaxKind(node.parent));
}
}
@@ -3053,11 +3055,11 @@ namespace ts {
* Gets the effective type parameters. If the node was parsed in a
* JavaScript file, gets the type parameters from the `@template` tag from JSDoc.
*/
- export function getEffectiveTypeParameterDeclarations(node: DeclarationWithTypeParameters): ReadonlyArray | undefined {
+ export function getEffectiveTypeParameterDeclarations(node: DeclarationWithTypeParameters) {
return node.typeParameters || (isInJavaScriptFile(node) ? getJSDocTypeParameterDeclarations(node) : undefined);
}
- export function getJSDocTypeParameterDeclarations(node: DeclarationWithTypeParameters): ReadonlyArray {
+ export function getJSDocTypeParameterDeclarations(node: DeclarationWithTypeParameters) {
const templateTag = getJSDocTemplateTag(node);
return templateTag && templateTag.typeParameters;
}
@@ -4926,6 +4928,10 @@ namespace ts {
return node.kind === SyntaxKind.LiteralType;
}
+ export function isImportTypeNode(node: Node): node is ImportTypeNode {
+ return node.kind === SyntaxKind.ImportType;
+ }
+
// Binding patterns
export function isObjectBindingPattern(node: Node): node is ObjectBindingPattern {
@@ -5606,8 +5612,7 @@ namespace ts {
|| kind === SyntaxKind.GetAccessor
|| kind === SyntaxKind.SetAccessor
|| kind === SyntaxKind.IndexSignature
- || kind === SyntaxKind.SemicolonClassElement
- || kind === SyntaxKind.MissingDeclaration;
+ || kind === SyntaxKind.SemicolonClassElement;
}
export function isClassLike(node: Node): node is ClassLikeDeclaration {
@@ -5638,8 +5643,7 @@ namespace ts {
|| kind === SyntaxKind.CallSignature
|| kind === SyntaxKind.PropertySignature
|| kind === SyntaxKind.MethodSignature
- || kind === SyntaxKind.IndexSignature
- || kind === SyntaxKind.MissingDeclaration;
+ || kind === SyntaxKind.IndexSignature;
}
export function isClassOrTypeElement(node: Node): node is ClassElement | TypeElement {
@@ -5653,8 +5657,7 @@ namespace ts {
|| kind === SyntaxKind.SpreadAssignment
|| kind === SyntaxKind.MethodDeclaration
|| kind === SyntaxKind.GetAccessor
- || kind === SyntaxKind.SetAccessor
- || kind === SyntaxKind.MissingDeclaration;
+ || kind === SyntaxKind.SetAccessor;
}
// Type
diff --git a/src/compiler/visitor.ts b/src/compiler/visitor.ts
index 7a70eb02e8e..284d870caa1 100644
--- a/src/compiler/visitor.ts
+++ b/src/compiler/visitor.ts
@@ -478,6 +478,7 @@ namespace ts {
case SyntaxKind.TaggedTemplateExpression:
return updateTaggedTemplate(node,
visitNode((node).tag, visitor, isExpression),
+ visitNodes((node).typeArguments, visitor, isExpression),
visitNode((node).template, visitor, isTemplateLiteral));
case SyntaxKind.TypeAssertionExpression:
diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts
index 951fa701157..a3f883a23b3 100644
--- a/src/harness/fourslash.ts
+++ b/src/harness/fourslash.ts
@@ -2113,14 +2113,11 @@ Actual: ${stringify(fullActual)}`);
this.raiseError("verifyRangesInImplementationList failed - expected to find at least one implementation location but got 0");
}
- for (let i = 0; i < implementations.length; i++) {
- for (let j = 0; j < implementations.length; j++) {
- if (i !== j && implementationsAreEqual(implementations[i], implementations[j])) {
- const { textSpan, fileName } = implementations[i];
- const end = textSpan.start + textSpan.length;
- this.raiseError(`Duplicate implementations returned for range (${textSpan.start}, ${end}) in ${fileName}`);
- }
- }
+ const duplicate = findDuplicatedElement(implementations, implementationsAreEqual);
+ if (duplicate) {
+ const { textSpan, fileName } = duplicate;
+ const end = textSpan.start + textSpan.length;
+ this.raiseError(`Duplicate implementations returned for range (${textSpan.start}, ${end}) in ${fileName}`);
}
const ranges = this.getRanges();
@@ -3766,6 +3763,16 @@ ${code}
function stripWhitespace(s: string): string {
return s.replace(/\s/g, "");
}
+
+ function findDuplicatedElement(a: ReadonlyArray, equal: (a: T, b: T) => boolean): T {
+ for (let i = 0; i < a.length; i++) {
+ for (let j = i + 1; j < a.length; j++) {
+ if (equal(a[i], a[j])) {
+ return a[i];
+ }
+ }
+ }
+ }
}
namespace FourSlashInterface {
diff --git a/src/harness/unittests/tsserverProjectSystem.ts b/src/harness/unittests/tsserverProjectSystem.ts
index b167ed21b94..a35f797eaa1 100644
--- a/src/harness/unittests/tsserverProjectSystem.ts
+++ b/src/harness/unittests/tsserverProjectSystem.ts
@@ -13,7 +13,9 @@ namespace ts.projectSystem {
export import checkArray = TestFSWithWatch.checkArray;
export import libFile = TestFSWithWatch.libFile;
export import checkWatchedFiles = TestFSWithWatch.checkWatchedFiles;
- import checkWatchedDirectories = TestFSWithWatch.checkWatchedDirectories;
+ export import checkWatchedFilesDetailed = TestFSWithWatch.checkWatchedFilesDetailed;
+ export import checkWatchedDirectories = TestFSWithWatch.checkWatchedDirectories;
+ export import checkWatchedDirectoriesDetailed = TestFSWithWatch.checkWatchedDirectoriesDetailed;
import safeList = TestFSWithWatch.safeList;
export const customTypesMap = {
@@ -7294,7 +7296,6 @@ namespace ts.projectSystem {
const host = createServerHost(files);
const session = createSession(host);
const projectService = session.getProjectService();
- debugger;
session.executeCommandSeq({
command: protocol.CommandTypes.Open,
arguments: {
@@ -7822,8 +7823,8 @@ namespace ts.projectSystem {
checkWatchedDirectories(host, emptyArray, /*recursive*/ true);
- TestFSWithWatch.checkMultiMapKeyCount("watchedFiles", host.watchedFiles, expectedWatchedFiles);
- TestFSWithWatch.checkMultiMapKeyCount("watchedDirectories", host.watchedDirectories, expectedWatchedDirectories);
+ checkWatchedFilesDetailed(host, expectedWatchedFiles);
+ checkWatchedDirectoriesDetailed(host, expectedWatchedDirectories, /*recursive*/ false);
checkProjectActualFiles(project, fileNames);
}
}
diff --git a/src/harness/unittests/typingsInstaller.ts b/src/harness/unittests/typingsInstaller.ts
index 1a19c98f477..a8c7d4895d1 100644
--- a/src/harness/unittests/typingsInstaller.ts
+++ b/src/harness/unittests/typingsInstaller.ts
@@ -141,7 +141,19 @@ namespace ts.projectSystem {
checkNumberOfProjects(projectService, { configuredProjects: 1 });
const p = configuredProjectAt(projectService, 0);
checkProjectActualFiles(p, [file1.path, tsconfig.path]);
- checkWatchedFiles(host, [tsconfig.path, libFile.path, packageJson.path, "/a/b/bower_components", "/a/b/node_modules"]);
+
+ const expectedWatchedFiles = createMap();
+ expectedWatchedFiles.set(tsconfig.path, 1); // tsserver
+ expectedWatchedFiles.set(libFile.path, 1); // tsserver
+ expectedWatchedFiles.set(packageJson.path, 1); // typing installer
+ checkWatchedFilesDetailed(host, expectedWatchedFiles);
+
+ checkWatchedDirectories(host, emptyArray, /*recursive*/ false);
+
+ const expectedWatchedDirectoriesRecursive = createMap();
+ expectedWatchedDirectoriesRecursive.set("/a/b", 2); // TypingInstaller and wild card
+ expectedWatchedDirectoriesRecursive.set("/a/b/node_modules/@types", 1); // type root watch
+ checkWatchedDirectoriesDetailed(host, expectedWatchedDirectoriesRecursive, /*recursive*/ true);
installer.installAll(/*expectedCount*/ 1);
@@ -149,7 +161,9 @@ namespace ts.projectSystem {
host.checkTimeoutQueueLengthAndRun(2);
checkProjectActualFiles(p, [file1.path, jquery.path, tsconfig.path]);
// should not watch jquery
- checkWatchedFiles(host, [tsconfig.path, libFile.path, packageJson.path, "/a/b/bower_components", "/a/b/node_modules"]);
+ checkWatchedFilesDetailed(host, expectedWatchedFiles);
+ checkWatchedDirectories(host, emptyArray, /*recursive*/ false);
+ checkWatchedDirectoriesDetailed(host, expectedWatchedDirectoriesRecursive, /*recursive*/ true);
});
it("inferred project (typings installed)", () => {
@@ -827,7 +841,17 @@ namespace ts.projectSystem {
checkNumberOfProjects(projectService, { configuredProjects: 1 });
const p = configuredProjectAt(projectService, 0);
checkProjectActualFiles(p, [app.path, jsconfig.path]);
- checkWatchedFiles(host, [jsconfig.path, "/bower_components", "/node_modules", libFile.path]);
+
+ const watchedFilesExpected = createMap();
+ watchedFilesExpected.set(jsconfig.path, 1); // project files
+ watchedFilesExpected.set(libFile.path, 1); // project files
+ checkWatchedFilesDetailed(host, watchedFilesExpected);
+
+ checkWatchedDirectories(host, emptyArray, /*recursive*/ false);
+
+ const watchedRecursiveDirectoriesExpected = createMap();
+ watchedRecursiveDirectoriesExpected.set("/", 2); // wild card + type installer
+ checkWatchedDirectoriesDetailed(host, watchedRecursiveDirectoriesExpected, /*recursive*/ true);
installer.installAll(/*expectedCount*/ 1);
@@ -999,14 +1023,14 @@ namespace ts.projectSystem {
proj.updateGraph();
assert.deepEqual(
- proj.getCachedUnresolvedImportsPerFile_TestOnly().get(f1.path),
+ proj.cachedUnresolvedImportsPerFile.get(f1.path),
["foo", "foo", "foo", "@bar/router", "@bar/common", "@bar/common"]
);
installer.installAll(/*expectedCount*/ 1);
});
- it("should recompute resolutions after typings are installed", () => {
+ it("cached unresolved typings are not recomputed if program structure did not change", () => {
const host = createServerHost([]);
const session = createSession(host);
const f = {
@@ -1029,7 +1053,7 @@ namespace ts.projectSystem {
const projectService = session.getProjectService();
checkNumberOfProjects(projectService, { inferredProjects: 1 });
const proj = projectService.inferredProjects[0];
- const version1 = proj.getCachedUnresolvedImportsPerFile_TestOnly().getVersion();
+ const version1 = proj.lastCachedUnresolvedImportsList;
// make a change that should not affect the structure of the program
const changeRequest: server.protocol.ChangeRequest = {
@@ -1047,8 +1071,8 @@ namespace ts.projectSystem {
};
session.executeCommand(changeRequest);
host.checkTimeoutQueueLengthAndRun(2); // This enqueues the updategraph and refresh inferred projects
- const version2 = proj.getCachedUnresolvedImportsPerFile_TestOnly().getVersion();
- assert.notEqual(version1, version2, "set of unresolved imports should change");
+ const version2 = proj.lastCachedUnresolvedImportsList;
+ assert.strictEqual(version1, version2, "set of unresolved imports should change");
});
it("expired cache entry (inferred project, should install typings)", () => {
@@ -1621,4 +1645,75 @@ namespace ts.projectSystem {
assert.deepEqual(commands, expectedCommands, "commands");
});
});
+
+ describe("recomputing resolutions of unresolved imports", () => {
+ const globalTypingsCacheLocation = "/tmp";
+ const appPath = "/a/b/app.js" as Path;
+ const foooPath = "/a/b/node_modules/fooo/index.d.ts";
+ function verifyResolvedModuleOfFooo(project: server.Project) {
+ const foooResolution = project.getLanguageService().getProgram().getSourceFileByPath(appPath).resolvedModules.get("fooo");
+ assert.equal(foooResolution.resolvedFileName, foooPath);
+ return foooResolution;
+ }
+
+ function verifyUnresolvedImportResolutions(appContents: string, typingNames: string[], typingFiles: FileOrFolder[]) {
+ const app: FileOrFolder = {
+ path: appPath,
+ content: `${appContents}import * as x from "fooo";`
+ };
+ const fooo: FileOrFolder = {
+ path: foooPath,
+ content: `export var x: string;`
+ };
+ const host = createServerHost([app, fooo]);
+ const installer = new (class extends Installer {
+ constructor() {
+ super(host, { globalTypingsCacheLocation, typesRegistry: createTypesRegistry("foo") });
+ }
+ installWorker(_requestId: number, _args: string[], _cwd: string, cb: TI.RequestCompletedAction) {
+ executeCommand(this, host, typingNames, typingFiles, cb);
+ }
+ })();
+ const projectService = createProjectService(host, { typingsInstaller: installer });
+ projectService.openClientFile(app.path);
+ projectService.checkNumberOfProjects({ inferredProjects: 1 });
+
+ const proj = projectService.inferredProjects[0];
+ checkProjectActualFiles(proj, [app.path, fooo.path]);
+ const foooResolution1 = verifyResolvedModuleOfFooo(proj);
+
+ installer.installAll(/*expectedCount*/ 1);
+ host.checkTimeoutQueueLengthAndRun(2);
+ checkProjectActualFiles(proj, typingFiles.map(f => f.path).concat(app.path, fooo.path));
+ const foooResolution2 = verifyResolvedModuleOfFooo(proj);
+ assert.strictEqual(foooResolution1, foooResolution2);
+ }
+
+ it("correctly invalidate the resolutions with typing names", () => {
+ verifyUnresolvedImportResolutions('import * as a from "foo";', ["foo"], [{
+ path: `${globalTypingsCacheLocation}/node_modules/foo/index.d.ts`,
+ content: "export function a(): void;"
+ }]);
+ });
+
+ it("correctly invalidate the resolutions with typing names that are trimmed", () => {
+ const fooAA: FileOrFolder = {
+ path: `${globalTypingsCacheLocation}/node_modules/foo/a/a.d.ts`,
+ content: "export function a (): void;"
+ };
+ const fooAB: FileOrFolder = {
+ path: `${globalTypingsCacheLocation}/node_modules/foo/a/b.d.ts`,
+ content: "export function b (): void;"
+ };
+ const fooAC: FileOrFolder = {
+ path: `${globalTypingsCacheLocation}/node_modules/foo/a/c.d.ts`,
+ content: "export function c (): void;"
+ };
+ verifyUnresolvedImportResolutions(`
+ import * as a from "foo/a/a";
+ import * as b from "foo/a/b";
+ import * as c from "foo/a/c";
+ `, ["foo"], [fooAA, fooAB, fooAC]);
+ });
+ });
}
diff --git a/src/harness/virtualFileSystemWithWatch.ts b/src/harness/virtualFileSystemWithWatch.ts
index 71b7da2ed14..3756f435d43 100644
--- a/src/harness/virtualFileSystemWithWatch.ts
+++ b/src/harness/virtualFileSystemWithWatch.ts
@@ -179,10 +179,18 @@ interface Array {}`
checkMapKeys("watchedFiles", host.watchedFiles, expectedFiles);
}
- export function checkWatchedDirectories(host: TestServerHost, expectedDirectories: string[], recursive = false) {
+ export function checkWatchedFilesDetailed(host: TestServerHost, expectedFiles: Map) {
+ checkMultiMapKeyCount("watchedFiles", host.watchedFiles, expectedFiles);
+ }
+
+ export function checkWatchedDirectories(host: TestServerHost, expectedDirectories: string[], recursive: boolean) {
checkMapKeys(`watchedDirectories${recursive ? " recursive" : ""}`, recursive ? host.watchedDirectoriesRecursive : host.watchedDirectories, expectedDirectories);
}
+ export function checkWatchedDirectoriesDetailed(host: TestServerHost, expectedDirectories: Map, recursive: boolean) {
+ checkMultiMapKeyCount(`watchedDirectories${recursive ? " recursive" : ""}`, recursive ? host.watchedDirectoriesRecursive : host.watchedDirectories, expectedDirectories);
+ }
+
export function checkOutputContains(host: TestServerHost, expected: ReadonlyArray) {
const mapExpected = arrayToSet(expected);
const mapSeen = createMap();
diff --git a/src/lib/es2015.promise.d.ts b/src/lib/es2015.promise.d.ts
index ab33531191f..14602c0b5ed 100644
--- a/src/lib/es2015.promise.d.ts
+++ b/src/lib/es2015.promise.d.ts
@@ -177,14 +177,7 @@ interface PromiseConstructor {
* @param reason The reason the promise was rejected.
* @returns A new rejected Promise.
*/
- reject(reason: any): Promise;
-
- /**
- * Creates a new rejected promise for the provided reason.
- * @param reason The reason the promise was rejected.
- * @returns A new rejected Promise.
- */
- reject(reason: any): Promise;
+ reject(reason?: any): Promise;
/**
* Creates a new resolved promise for the provided value.
diff --git a/src/loc/lcl/chs/diagnosticMessages/diagnosticMessages.generated.json.lcl b/src/loc/lcl/chs/diagnosticMessages/diagnosticMessages.generated.json.lcl
index c2b2cf32cde..b6189dcec82 100644
--- a/src/loc/lcl/chs/diagnosticMessages/diagnosticMessages.generated.json.lcl
+++ b/src/loc/lcl/chs/diagnosticMessages/diagnosticMessages.generated.json.lcl
@@ -2388,15 +2388,6 @@
- -
-
-
-
-
-
-
-
-
-
@@ -3771,20 +3762,20 @@
- -
+
-
-
+
-
+
- -
+
-
-
+
-
+
diff --git a/src/loc/lcl/deu/diagnosticMessages/diagnosticMessages.generated.json.lcl b/src/loc/lcl/deu/diagnosticMessages/diagnosticMessages.generated.json.lcl
index 58326d7ccb6..b2ffdf585e0 100644
--- a/src/loc/lcl/deu/diagnosticMessages/diagnosticMessages.generated.json.lcl
+++ b/src/loc/lcl/deu/diagnosticMessages/diagnosticMessages.generated.json.lcl
@@ -3894,6 +3894,15 @@
+ -
+
+
+
+
+
+
+
+
-
@@ -6003,6 +6012,9 @@
-
+
+
+
diff --git a/src/loc/lcl/ita/diagnosticMessages/diagnosticMessages.generated.json.lcl b/src/loc/lcl/ita/diagnosticMessages/diagnosticMessages.generated.json.lcl
index f68671fdc91..c53a7b86387 100644
--- a/src/loc/lcl/ita/diagnosticMessages/diagnosticMessages.generated.json.lcl
+++ b/src/loc/lcl/ita/diagnosticMessages/diagnosticMessages.generated.json.lcl
@@ -2388,15 +2388,6 @@
- -
-
-
-
-
-
-
-
-
-
@@ -3771,20 +3762,20 @@
- -
+
-
-
+
-
+
- -
+
-
-
+
-
+
diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts
index 670fb1be124..367ccebc6ff 100644
--- a/src/server/editorServices.ts
+++ b/src/server/editorServices.ts
@@ -312,7 +312,8 @@ namespace ts.server {
export class ProjectService {
- public readonly typingsCache: TypingsCache;
+ /*@internal*/
+ readonly typingsCache: TypingsCache;
private readonly documentRegistry: DocumentRegistry;
@@ -527,13 +528,13 @@ namespace ts.server {
}
switch (response.kind) {
case ActionSet:
- project.resolutionCache.clear();
- this.typingsCache.updateTypingsForProject(response.projectName, response.compilerOptions, response.typeAcquisition, response.unresolvedImports, response.typings);
+ // Update the typing files and update the project
+ project.updateTypingFiles(this.typingsCache.updateTypingsForProject(response.projectName, response.compilerOptions, response.typeAcquisition, response.unresolvedImports, response.typings));
break;
case ActionInvalidate:
- project.resolutionCache.clear();
- this.typingsCache.deleteTypingsForProject(response.projectName);
- break;
+ // Do not clear resolution cache, there was changes detected in typings, so enque typing request and let it get us correct results
+ this.typingsCache.enqueueInstallTypingsForProject(project, project.lastCachedUnresolvedImportsList, /*forceRefresh*/ true);
+ return;
}
this.delayUpdateProjectGraphAndEnsureProjectStructureForOpenFiles(project);
}
diff --git a/src/server/project.ts b/src/server/project.ts
index 1502d107dee..92645cfca52 100644
--- a/src/server/project.ts
+++ b/src/server/project.ts
@@ -55,34 +55,6 @@ namespace ts.server {
projectErrors: ReadonlyArray;
}
- export class UnresolvedImportsMap {
- readonly perFileMap = createMap>();
- private version = 0;
-
- public clear() {
- this.perFileMap.clear();
- this.version = 0;
- }
-
- public getVersion() {
- return this.version;
- }
-
- public remove(path: Path) {
- this.perFileMap.delete(path);
- this.version++;
- }
-
- public get(path: Path) {
- return this.perFileMap.get(path);
- }
-
- public set(path: Path, value: ReadonlyArray) {
- this.perFileMap.set(path, value);
- this.version++;
- }
- }
-
export interface PluginCreateInfo {
project: Project;
languageService: LanguageService;
@@ -116,8 +88,18 @@ namespace ts.server {
private missingFilesMap: Map;
private plugins: PluginModule[] = [];
- private cachedUnresolvedImportsPerFile = new UnresolvedImportsMap();
- private lastCachedUnresolvedImportsList: SortedReadonlyArray;
+ /*@internal*/
+ /**
+ * This is map from files to unresolved imports in it
+ * Maop does not contain entries for files that do not have unresolved imports
+ * This helps in containing the set of files to invalidate
+ */
+ cachedUnresolvedImportsPerFile = createMap>();
+
+ /*@internal*/
+ lastCachedUnresolvedImportsList: SortedReadonlyArray;
+ /*@internal*/
+ private hasAddedorRemovedFiles = false;
private lastFileExceededProgramSize: string | undefined;
@@ -149,10 +131,10 @@ namespace ts.server {
*/
private lastReportedVersion = 0;
/**
- * Current project structure version.
+ * Current project's program version. (incremented everytime new program is created that is not complete reuse from the old one)
* This property is changed in 'updateGraph' based on the set of files in program
*/
- private projectStructureVersion = 0;
+ private projectProgramVersion = 0;
/**
* Current version of the project state. It is changed when:
* - new root file was added/removed
@@ -167,7 +149,8 @@ namespace ts.server {
/*@internal*/
hasChangedAutomaticTypeDirectiveNames = false;
- private typingFiles: SortedReadonlyArray;
+ /*@internal*/
+ typingFiles: SortedReadonlyArray = emptyArray;
private readonly cancellationToken: ThrottledCancellationToken;
@@ -181,10 +164,6 @@ namespace ts.server {
return hasOneOrMoreJsAndNoTsFiles(this);
}
- public getCachedUnresolvedImportsPerFile_TestOnly() {
- return this.cachedUnresolvedImportsPerFile;
- }
-
public static resolveModule(moduleName: string, initialDir: string, host: ServerHost, log: (message: string) => void): {} {
const resolvedPath = normalizeSlashes(host.resolvePath(combinePaths(initialDir, "node_modules")));
log(`Loading ${moduleName} from ${initialDir} (resolved to ${resolvedPath})`);
@@ -742,7 +721,7 @@ namespace ts.server {
else {
this.resolutionCache.invalidateResolutionOfFile(info.path);
}
- this.cachedUnresolvedImportsPerFile.remove(info.path);
+ this.cachedUnresolvedImportsPerFile.delete(info.path);
if (detachFromProject) {
info.detachFromProject(this);
@@ -763,16 +742,13 @@ namespace ts.server {
}
/* @internal */
- private extractUnresolvedImportsFromSourceFile(file: SourceFile, result: Push, ambientModules: string[]) {
+ private extractUnresolvedImportsFromSourceFile(file: SourceFile, ambientModules: string[]): ReadonlyArray {
const cached = this.cachedUnresolvedImportsPerFile.get(file.path);
if (cached) {
- // found cached result - use it and return
- for (const f of cached) {
- result.push(f);
- }
- return;
+ // found cached result, return
+ return cached;
}
- let unresolvedImports: string[];
+ let unresolvedImports: string[] | undefined;
if (file.resolvedModules) {
file.resolvedModules.forEach((resolvedModule, name) => {
// pick unresolved non-relative names
@@ -788,17 +764,23 @@ namespace ts.server {
trimmed = trimmed.substr(0, i);
}
(unresolvedImports || (unresolvedImports = [])).push(trimmed);
- result.push(trimmed);
}
});
}
+
this.cachedUnresolvedImportsPerFile.set(file.path, unresolvedImports || emptyArray);
+ return unresolvedImports || emptyArray;
function isAmbientlyDeclaredModule(name: string) {
return ambientModules.some(m => m === name);
}
}
+ /* @internal */
+ onFileAddedOrRemoved() {
+ this.hasAddedorRemovedFiles = true;
+ }
+
/**
* Updates set of files that contribute to this project
* @returns: true if set of files in the project stays the same and false - otherwise.
@@ -806,13 +788,15 @@ namespace ts.server {
updateGraph(): boolean {
this.resolutionCache.startRecordingFilesWithChangedResolutions();
- let hasChanges = this.updateGraphWorker();
+ const hasNewProgram = this.updateGraphWorker();
+ const hasAddedorRemovedFiles = this.hasAddedorRemovedFiles;
+ this.hasAddedorRemovedFiles = false;
const changedFiles: ReadonlyArray = this.resolutionCache.finishRecordingFilesWithChangedResolutions() || emptyArray;
for (const file of changedFiles) {
// delete cached information for changed files
- this.cachedUnresolvedImportsPerFile.remove(file);
+ this.cachedUnresolvedImportsPerFile.delete(file);
}
// update builder only if language service is enabled
@@ -824,30 +808,35 @@ namespace ts.server {
// 3. new files were added/removed, but compilation settings stays the same - collect unresolved imports for all new/modified files
// (can reuse cached imports for files that were not changed)
// 4. compilation settings were changed in the way that might affect module resolution - drop all caches and collect all data from the scratch
- if (hasChanges || changedFiles.length) {
- const result: string[] = [];
+ if (hasNewProgram || changedFiles.length) {
+ let result: string[] | undefined;
const ambientModules = this.program.getTypeChecker().getAmbientModules().map(mod => stripQuotes(mod.getName()));
for (const sourceFile of this.program.getSourceFiles()) {
- this.extractUnresolvedImportsFromSourceFile(sourceFile, result, ambientModules);
+ const unResolved = this.extractUnresolvedImportsFromSourceFile(sourceFile, ambientModules);
+ if (unResolved !== emptyArray) {
+ (result || (result = [])).push(...unResolved);
+ }
}
- this.lastCachedUnresolvedImportsList = toDeduplicatedSortedArray(result);
+ this.lastCachedUnresolvedImportsList = result ? toDeduplicatedSortedArray(result) : emptyArray;
}
- const cachedTypings = this.projectService.typingsCache.getTypingsForProject(this, this.lastCachedUnresolvedImportsList, hasChanges);
- if (!arrayIsEqualTo(this.typingFiles, cachedTypings)) {
- this.typingFiles = cachedTypings;
- this.markAsDirty();
- hasChanges = this.updateGraphWorker() || hasChanges;
- }
+ this.projectService.typingsCache.enqueueInstallTypingsForProject(this, this.lastCachedUnresolvedImportsList, hasAddedorRemovedFiles);
}
else {
this.lastCachedUnresolvedImportsList = undefined;
}
- if (hasChanges) {
- this.projectStructureVersion++;
+ if (hasNewProgram) {
+ this.projectProgramVersion++;
}
- return !hasChanges;
+ return !hasNewProgram;
+ }
+
+ /*@internal*/
+ updateTypingFiles(typingFiles: SortedReadonlyArray) {
+ this.typingFiles = typingFiles;
+ // Invalidate files with unresolved imports
+ this.resolutionCache.setFilesWithInvalidatedNonRelativeUnresolvedImports(this.cachedUnresolvedImportsPerFile);
}
/* @internal */
@@ -876,9 +865,9 @@ namespace ts.server {
// bump up the version if
// - oldProgram is not set - this is a first time updateGraph is called
// - newProgram is different from the old program and structure of the old program was not reused.
- const hasChanges = this.program && (!oldProgram || (this.program !== oldProgram && !(oldProgram.structureIsReused & StructureIsReused.Completely)));
+ const hasNewProgram = this.program && (!oldProgram || (this.program !== oldProgram && !(oldProgram.structureIsReused & StructureIsReused.Completely)));
this.hasChangedAutomaticTypeDirectiveNames = false;
- if (hasChanges) {
+ if (hasNewProgram) {
if (oldProgram) {
for (const f of oldProgram.getSourceFiles()) {
if (this.program.getSourceFileByPath(f.path)) {
@@ -916,8 +905,8 @@ namespace ts.server {
removed => this.detachScriptInfoFromProject(removed)
);
const elapsed = timestamp() - start;
- this.writeLog(`Finishing updateGraphWorker: Project: ${this.getProjectName()} Version: ${this.getProjectVersion()} structureChanged: ${hasChanges} Elapsed: ${elapsed}ms`);
- return hasChanges;
+ this.writeLog(`Finishing updateGraphWorker: Project: ${this.getProjectName()} Version: ${this.getProjectVersion()} structureChanged: ${hasNewProgram} Elapsed: ${elapsed}ms`);
+ return hasNewProgram;
}
private detachScriptInfoFromProject(uncheckedFileName: string) {
@@ -985,15 +974,13 @@ namespace ts.server {
setCompilerOptions(compilerOptions: CompilerOptions) {
if (compilerOptions) {
compilerOptions.allowNonTsExtensions = true;
- if (changesAffectModuleResolution(this.compilerOptions, compilerOptions)) {
- // reset cached unresolved imports if changes in compiler options affected module resolution
- this.cachedUnresolvedImportsPerFile.clear();
- this.lastCachedUnresolvedImportsList = undefined;
- }
const oldOptions = this.compilerOptions;
this.compilerOptions = compilerOptions;
this.setInternalCompilerOptionsForEmittingJsFiles();
if (changesAffectModuleResolution(oldOptions, compilerOptions)) {
+ // reset cached unresolved imports if changes in compiler options affected module resolution
+ this.cachedUnresolvedImportsPerFile.clear();
+ this.lastCachedUnresolvedImportsList = undefined;
this.resolutionCache.clear();
}
this.markAsDirty();
@@ -1006,7 +993,7 @@ namespace ts.server {
const info: protocol.ProjectVersionInfo = {
projectName: this.getProjectName(),
- version: this.projectStructureVersion,
+ version: this.projectProgramVersion,
isInferred: this.projectKind === ProjectKind.Inferred,
options: this.getCompilationSettings(),
languageServiceDisabled: !this.languageServiceEnabled,
@@ -1017,7 +1004,7 @@ namespace ts.server {
// check if requested version is the same that we have reported last time
if (this.lastReportedFileNames && lastKnownVersion === this.lastReportedVersion) {
// if current structure version is the same - return info without any changes
- if (this.projectStructureVersion === this.lastReportedVersion && !updatedFileNames) {
+ if (this.projectProgramVersion === this.lastReportedVersion && !updatedFileNames) {
return { info, projectErrors: this.getGlobalProjectErrors() };
}
// compute and return the difference
@@ -1040,7 +1027,7 @@ namespace ts.server {
}
});
this.lastReportedFileNames = currentFiles;
- this.lastReportedVersion = this.projectStructureVersion;
+ this.lastReportedVersion = this.projectProgramVersion;
return { info, changes: { added, removed, updated }, projectErrors: this.getGlobalProjectErrors() };
}
else {
@@ -1049,7 +1036,7 @@ namespace ts.server {
const externalFiles = this.getExternalFiles().map(f => toNormalizedPath(f));
const allFiles = projectFileNames.concat(externalFiles);
this.lastReportedFileNames = arrayToSet(allFiles);
- this.lastReportedVersion = this.projectStructureVersion;
+ this.lastReportedVersion = this.projectProgramVersion;
return { info, files: allFiles, projectErrors: this.getGlobalProjectErrors() };
}
}
diff --git a/src/server/scriptInfo.ts b/src/server/scriptInfo.ts
index db56973d796..589975769b1 100644
--- a/src/server/scriptInfo.ts
+++ b/src/server/scriptInfo.ts
@@ -304,6 +304,7 @@ namespace ts.server {
const isNew = !this.isAttached(project);
if (isNew) {
this.containingProjects.push(project);
+ project.onFileAddedOrRemoved();
if (!project.getCompilerOptions().preserveSymlinks) {
this.ensureRealPath();
}
@@ -328,19 +329,24 @@ namespace ts.server {
return;
case 1:
if (this.containingProjects[0] === project) {
+ project.onFileAddedOrRemoved();
this.containingProjects.pop();
}
break;
case 2:
if (this.containingProjects[0] === project) {
+ project.onFileAddedOrRemoved();
this.containingProjects[0] = this.containingProjects.pop();
}
else if (this.containingProjects[1] === project) {
+ project.onFileAddedOrRemoved();
this.containingProjects.pop();
}
break;
default:
- unorderedRemoveItem(this.containingProjects, project);
+ if (unorderedRemoveItem(this.containingProjects, project)) {
+ project.onFileAddedOrRemoved();
+ }
break;
}
}
diff --git a/src/server/session.ts b/src/server/session.ts
index 1076d17e11f..27044ec369d 100644
--- a/src/server/session.ts
+++ b/src/server/session.ts
@@ -634,7 +634,8 @@ namespace ts.server {
code: d.code,
source: d.source,
startLocation: scriptInfo && scriptInfo.positionToLineOffset(d.start),
- endLocation: scriptInfo && scriptInfo.positionToLineOffset(d.start + d.length)
+ endLocation: scriptInfo && scriptInfo.positionToLineOffset(d.start + d.length),
+ reportsUnnecessary: d.reportsUnnecessary
});
}
diff --git a/src/server/types.ts b/src/server/types.ts
index d4ddd81c53e..184a121522e 100644
--- a/src/server/types.ts
+++ b/src/server/types.ts
@@ -119,8 +119,10 @@ declare namespace ts.server {
/* @internal */
export interface InstallTypingHost extends JsTyping.TypingResolutionHost {
+ useCaseSensitiveFileNames: boolean;
writeFile(path: string, content: string): void;
createDirectory(path: string): void;
watchFile?(path: string, callback: FileWatcherCallback, pollingInterval?: number): FileWatcher;
+ watchDirectory?(path: string, callback: DirectoryWatcherCallback, recursive?: boolean): FileWatcher;
}
}
diff --git a/src/server/typingsCache.ts b/src/server/typingsCache.ts
index f2642230f2d..c255757481f 100644
--- a/src/server/typingsCache.ts
+++ b/src/server/typingsCache.ts
@@ -24,7 +24,7 @@ namespace ts.server {
globalTypingsCacheLocation: undefined
};
- class TypingsCacheEntry {
+ interface TypingsCacheEntry {
readonly typeAcquisition: TypeAcquisition;
readonly compilerOptions: CompilerOptions;
readonly typings: SortedReadonlyArray;
@@ -80,6 +80,7 @@ namespace ts.server {
return !arrayIsEqualTo(imports1, imports2);
}
+ /*@internal*/
export class TypingsCache {
private readonly perProjectCache: Map = createMap();
@@ -94,15 +95,14 @@ namespace ts.server {
return this.installer.installPackage(options);
}
- getTypingsForProject(project: Project, unresolvedImports: SortedReadonlyArray, forceRefresh: boolean): SortedReadonlyArray {
+ enqueueInstallTypingsForProject(project: Project, unresolvedImports: SortedReadonlyArray, forceRefresh: boolean) {
const typeAcquisition = project.getTypeAcquisition();
if (!typeAcquisition || !typeAcquisition.enable) {
- return emptyArray;
+ return;
}
const entry = this.perProjectCache.get(project.getProjectName());
- const result: SortedReadonlyArray = entry ? entry.typings : emptyArray;
if (forceRefresh ||
!entry ||
typeAcquisitionChanged(typeAcquisition, entry.typeAcquisition) ||
@@ -113,28 +113,25 @@ namespace ts.server {
this.perProjectCache.set(project.getProjectName(), {
compilerOptions: project.getCompilationSettings(),
typeAcquisition,
- typings: result,
+ typings: entry ? entry.typings : emptyArray,
unresolvedImports,
poisoned: true
});
// something has been changed, issue a request to update typings
this.installer.enqueueInstallTypingsRequest(project, typeAcquisition, unresolvedImports);
}
- return result;
}
updateTypingsForProject(projectName: string, compilerOptions: CompilerOptions, typeAcquisition: TypeAcquisition, unresolvedImports: SortedReadonlyArray, newTypings: string[]) {
+ const typings = toSortedArray(newTypings);
this.perProjectCache.set(projectName, {
compilerOptions,
typeAcquisition,
- typings: toSortedArray(newTypings),
+ typings,
unresolvedImports,
poisoned: false
});
- }
-
- deleteTypingsForProject(projectName: string) {
- this.perProjectCache.delete(projectName);
+ return !typeAcquisition || !typeAcquisition.enable ? emptyArray : typings;
}
onProjectClosed(project: Project) {
diff --git a/src/server/typingsInstaller/typingsInstaller.ts b/src/server/typingsInstaller/typingsInstaller.ts
index 8d482241d1b..7d8cf02fab0 100644
--- a/src/server/typingsInstaller/typingsInstaller.ts
+++ b/src/server/typingsInstaller/typingsInstaller.ts
@@ -64,13 +64,31 @@ namespace ts.server.typingsInstaller {
onRequestCompleted: RequestCompletedAction;
}
+ function isPackageOrBowerJson(fileName: string) {
+ const base = getBaseFileName(fileName);
+ return base === "package.json" || base === "bower.json";
+ }
+
+ function getDirectoryExcludingNodeModulesOrBowerComponents(f: string) {
+ const indexOfNodeModules = f.indexOf("/node_modules/");
+ const indexOfBowerComponents = f.indexOf("/bower_components/");
+ const subStrLength = indexOfNodeModules === -1 || indexOfBowerComponents === -1 ?
+ Math.max(indexOfNodeModules, indexOfBowerComponents) :
+ Math.min(indexOfNodeModules, indexOfBowerComponents);
+ return subStrLength === -1 ? f : f.substr(0, subStrLength);
+ }
+
+ type ProjectWatchers = Map & { isInvoked?: boolean; };
+
export abstract class TypingsInstaller {
private readonly packageNameToTypingLocation: Map = createMap();
private readonly missingTypingsSet: Map = createMap();
private readonly knownCachesSet: Map = createMap();
- private readonly projectWatchers = createMap