diff --git a/.gitignore b/.gitignore
index 9d0bb4d0c6f..04c397bb7e3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -44,6 +44,7 @@ scripts/ior.js
scripts/authors.js
scripts/configurePrerelease.js
scripts/open-user-pr.js
+scripts/open-cherry-pick-pr.js
scripts/processDiagnosticMessages.d.ts
scripts/processDiagnosticMessages.js
scripts/produceLKG.js
diff --git a/package.json b/package.json
index a50a79aff13..965c0c3bb32 100644
--- a/package.json
+++ b/package.json
@@ -2,7 +2,7 @@
"name": "typescript",
"author": "Microsoft Corp.",
"homepage": "https://www.typescriptlang.org/",
- "version": "3.5.0",
+ "version": "3.6.0",
"license": "Apache-2.0",
"description": "TypeScript is a language for application scale JavaScript development",
"keywords": [
@@ -48,11 +48,13 @@
"@types/mocha": "latest",
"@types/ms": "latest",
"@types/node": "8.5.5",
+ "@types/node-fetch": "^2.3.4",
"@types/q": "latest",
"@types/source-map-support": "latest",
"@types/through2": "latest",
"@types/travis-fold": "latest",
"@types/xml2js": "^0.4.0",
+ "azure-devops-node-api": "^8.0.0",
"browser-resolve": "^1.11.2",
"browserify": "latest",
"chai": "latest",
@@ -74,6 +76,7 @@
"mocha": "latest",
"mocha-fivemat-progress-reporter": "latest",
"ms": "latest",
+ "node-fetch": "^2.6.0",
"plugin-error": "latest",
"pretty-hrtime": "^1.0.3",
"prex": "^0.4.3",
diff --git a/scripts/open-cherry-pick-pr.ts b/scripts/open-cherry-pick-pr.ts
new file mode 100644
index 00000000000..8bdeb05e4a2
--- /dev/null
+++ b/scripts/open-cherry-pick-pr.ts
@@ -0,0 +1,105 @@
+///
+// Must reference esnext.asynciterable lib, since octokit uses AsyncIterable internally
+///
+
+import Octokit = require("@octokit/rest");
+const {runSequence} = require("./run-sequence");
+import fs = require("fs");
+import path = require("path");
+
+const userName = process.env.GH_USERNAME;
+const reviewers = process.env.REQUESTING_USER ? [process.env.REQUESTING_USER] : ["weswigham", "RyanCavanaugh"];
+const branchName = `pick/${process.env.SOURCE_ISSUE}/${process.env.TARGET_BRANCH}`;
+const remoteUrl = `https://${process.argv[2]}@github.com/${userName}/TypeScript.git`;
+
+async function main() {
+ if (!process.env.TARGET_BRANCH) {
+ throw new Error("Target branch not specified");
+ }
+ if (!process.env.SOURCE_ISSUE) {
+ throw new Error("Source issue not specified");
+ }
+ const currentSha = runSequence([
+ ["git", ["rev-parse", "HEAD"]]
+ ]);
+ const currentAuthor = runSequence([
+ ["git", ["log", "-1", `--pretty="%aN <%aE>"`]]
+ ]);
+ runSequence([
+ ["git", ["fetch", "origin", "master"]]
+ ]);
+ let logText = runSequence([
+ ["git", ["log", `origin/master..${currentSha.trim()}`, `--pretty="%h %s%n%b"`, "--reverse"]]
+ ]);
+ logText = `Cherry-pick PR #${process.env.SOURCE_ISSUE} into ${process.env.TARGET_BRANCH}
+
+Component commits:
+${logText.trim()}`
+ const logpath = path.join(__dirname, "../", "logmessage.txt");
+ runSequence([
+ ["git", ["checkout", "-b", "temp-branch"]],
+ ["git", ["reset", "origin/master", "--soft"]]
+ ]);
+ fs.writeFileSync(logpath, logText);
+ runSequence([
+ ["git", ["commit", "-F", logpath, `--author="${currentAuthor.trim()}"`]]
+ ]);
+ fs.unlinkSync(logpath);
+ const squashSha = runSequence([
+ ["git", ["rev-parse", "HEAD"]]
+ ]);
+ runSequence([
+ ["git", ["checkout", process.env.TARGET_BRANCH]], // checkout the target branch
+ ["git", ["checkout", "-b", branchName]], // create a new branch
+ ["git", ["cherry-pick", squashSha.trim()]], //
+ ["git", ["remote", "add", "fork", remoteUrl]], // Add the remote fork
+ ["git", ["push", "--set-upstream", "fork", branchName, "-f"]] // push the branch
+ ]);
+
+ const gh = new Octokit();
+ gh.authenticate({
+ type: "token",
+ token: process.argv[2]
+ });
+ const r = await gh.pulls.create({
+ owner: "Microsoft",
+ repo: "TypeScript",
+ maintainer_can_modify: true,
+ title: `🤖 Cherry-pick PR #${process.env.SOURCE_ISSUE} into ${process.env.TARGET_BRANCH}`,
+ head: `${userName}:${branchName}`,
+ base: process.env.TARGET_BRANCH,
+ body:
+ `This cherry-pick was triggerd by a request on https://github.com/Microsoft/TypeScript/pull/${process.env.SOURCE_ISSUE}
+Please review the diff and merge if no changes are unexpected.
+You can view the cherry-pick log [here](https://typescript.visualstudio.com/TypeScript/_build/index?buildId=${process.env.BUILD_BUILDID}&_a=summary).
+
+cc ${reviewers.map(r => "@" + r).join(" ")}`,
+ });
+ const num = r.data.number;
+ console.log(`Pull request ${num} created.`);
+
+ await gh.issues.createComment({
+ number: +process.env.SOURCE_ISSUE,
+ owner: "Microsoft",
+ repo: "TypeScript",
+ body: `Hey @${process.env.REQUESTING_USER}, I've opened #${num} for you.`
+ });
+}
+
+main().catch(async e => {
+ console.error(e);
+ process.exitCode = 1;
+ if (process.env.SOURCE_ISSUE) {
+ const gh = new Octokit();
+ gh.authenticate({
+ type: "token",
+ token: process.argv[2]
+ });
+ await gh.issues.createComment({
+ number: +process.env.SOURCE_ISSUE,
+ owner: "Microsoft",
+ repo: "TypeScript",
+ body: `Hey @${process.env.REQUESTING_USER}, I couldn't open a PR with the cherry-pick. ([You can check the log here](https://typescript.visualstudio.com/TypeScript/_build/index?buildId=${process.env.BUILD_BUILDID}&_a=summary)). You may need to squash and pick this PR into ${process.env.TARGET_BRANCH} manually.`
+ });
+ }
+});
\ No newline at end of file
diff --git a/scripts/open-user-pr.ts b/scripts/open-user-pr.ts
index 0a636c267b8..23e4bb951d7 100644
--- a/scripts/open-user-pr.ts
+++ b/scripts/open-user-pr.ts
@@ -9,7 +9,7 @@ function padNum(number: number) {
}
const userName = process.env.GH_USERNAME;
-const reviewers = process.env.requesting_user ? [process.env.requesting_user] : ["weswigham", "sandersn", "RyanCavanaugh"];
+const reviewers = process.env.REQUESTING_USER ? [process.env.REQUESTING_USER] : ["weswigham", "sandersn", "RyanCavanaugh"];
const now = new Date();
const branchName = `user-update-${process.env.TARGET_FORK}-${now.getFullYear()}${padNum(now.getMonth())}${padNum(now.getDay())}${process.env.TARGET_BRANCH ? "-" + process.env.TARGET_BRANCH : ""}`;
const remoteUrl = `https://${process.argv[2]}@github.com/${userName}/TypeScript.git`;
@@ -36,14 +36,14 @@ gh.pulls.create({
head: `${userName}:${branchName}`,
base: process.env.TARGET_BRANCH || "master",
body:
-`${process.env.source_issue ? `This test run was triggerd by a request on https://github.com/Microsoft/TypeScript/pull/${process.env.source_issue} `+"\n" : ""}Please review the diff and merge if no changes are unexpected.
+`${process.env.SOURCE_ISSUE ? `This test run was triggerd by a request on https://github.com/Microsoft/TypeScript/pull/${process.env.SOURCE_ISSUE} `+"\n" : ""}Please review the diff and merge if no changes are unexpected.
You can view the build log [here](https://typescript.visualstudio.com/TypeScript/_build/index?buildId=${process.env.BUILD_BUILDID}&_a=summary).
cc ${reviewers.map(r => "@" + r).join(" ")}`,
}).then(async r => {
const num = r.data.number;
console.log(`Pull request ${num} created.`);
- if (!process.env.source_issue) {
+ if (!process.env.SOURCE_ISSUE) {
await gh.pulls.createReviewRequest({
owner: process.env.TARGET_FORK,
repo: "TypeScript",
@@ -53,7 +53,7 @@ cc ${reviewers.map(r => "@" + r).join(" ")}`,
}
else {
await gh.issues.createComment({
- number: +process.env.source_issue,
+ number: +process.env.SOURCE_ISSUE,
owner: "Microsoft",
repo: "TypeScript",
body: `The user suite test run you requested has finished and _failed_. I've opened a [PR with the baseline diff from master](${r.data.html_url}).`
diff --git a/scripts/post-vsts-artifact-comment.js b/scripts/post-vsts-artifact-comment.js
new file mode 100644
index 00000000000..6d84294bcfe
--- /dev/null
+++ b/scripts/post-vsts-artifact-comment.js
@@ -0,0 +1,64 @@
+// @ts-check
+///
+// Must reference esnext.asynciterable lib, since octokit uses AsyncIterable internally
+const Octokit = require("@octokit/rest");
+const ado = require("azure-devops-node-api");
+const { default: fetch } = require("node-fetch");
+
+async function main() {
+ if (!process.env.SOURCE_ISSUE) {
+ throw new Error("No source issue specified");
+ }
+ if (!process.env.BUILD_BUILDID) {
+ throw new Error("No build ID specified");
+ }
+ // The pipelines API does _not_ make getting the direct URL to a specific file _within_ an artifact trivial
+ const cli = new ado.WebApi("https://typescript.visualstudio.com/defaultcollection", ado.getHandlerFromToken("")); // Empty token, anon auth
+ const build = await cli.getBuildApi();
+ const artifact = await build.getArtifact("typescript", +process.env.BUILD_BUILDID, "tgz");
+ const updatedUrl = new URL(artifact.resource.url);
+ updatedUrl.search = `artifactName=tgz&fileId=${artifact.resource.data}&fileName=manifest`;
+ const resp = await (await fetch(`${updatedUrl}`)).json();
+ const file = resp.items[0];
+ const tgzUrl = new URL(artifact.resource.url);
+ tgzUrl.search = `artifactName=tgz&fileId=${file.blob.id}&fileName=${file.path}`;
+ const link = "" + tgzUrl;
+ const gh = new Octokit();
+ gh.authenticate({
+ type: "token",
+ token: process.argv[2]
+ });
+ await gh.issues.createComment({
+ number: +process.env.SOURCE_ISSUE,
+ owner: "Microsoft",
+ repo: "TypeScript",
+ body: `Hey @${process.env.REQUESTING_USER}, I've packed this into [an installable tgz](${link}). You can install it for testing by referencing it in your \`package.json\` like so:
+\`\`\`
+{
+ "devDependencies": {
+ "typescript": "${link}"
+ }
+}
+\`\`\`
+and then running \`npm install\`.
+`
+ });
+}
+
+main().catch(async e => {
+ console.error(e);
+ process.exitCode = 1;
+ if (process.env.SOURCE_ISSUE) {
+ const gh = new Octokit();
+ gh.authenticate({
+ type: "token",
+ token: process.argv[2]
+ });
+ await gh.issues.createComment({
+ number: +process.env.SOURCE_ISSUE,
+ owner: "Microsoft",
+ repo: "TypeScript",
+ body: `Hey @${process.env.REQUESTING_USER}, something went wrong when looking for the build artifact. ([You can check the log here](https://typescript.visualstudio.com/TypeScript/_build/index?buildId=${process.env.BUILD_BUILDID}&_a=summary)).`
+ });
+ }
+});
\ No newline at end of file
diff --git a/scripts/run-sequence.js b/scripts/run-sequence.js
index ef7a384af4c..f014cde1b91 100644
--- a/scripts/run-sequence.js
+++ b/scripts/run-sequence.js
@@ -5,12 +5,13 @@ const cp = require("child_process");
* @param {[string, string[]][]} tasks
* @param {cp.SpawnSyncOptions} opts
*/
-function runSequence(tasks, opts = { timeout: 100000, shell: true, stdio: "inherit" }) {
+function runSequence(tasks, opts = { timeout: 100000, shell: true }) {
let lastResult;
for (const task of tasks) {
console.log(`${task[0]} ${task[1].join(" ")}`);
const result = cp.spawnSync(task[0], task[1], opts);
if (result.status !== 0) throw new Error(`${task[0]} ${task[1].join(" ")} failed: ${result.stderr && result.stderr.toString()}`);
+ console.log(result.stdout && result.stdout.toString());
lastResult = result;
}
return lastResult && lastResult.stdout && lastResult.stdout.toString();
diff --git a/scripts/update-experimental-branches.js b/scripts/update-experimental-branches.js
index c112cf1a367..b734c8a6fd8 100644
--- a/scripts/update-experimental-branches.js
+++ b/scripts/update-experimental-branches.js
@@ -1,87 +1,91 @@
// @ts-check
///
const Octokit = require("@octokit/rest");
-const {runSequence} = require("./run-sequence");
+const { runSequence } = require("./run-sequence");
+
+// The first is used by bot-based kickoffs, the second by automatic triggers
+const triggeredPR = process.env.SOURCE_ISSUE || process.env.SYSTEM_PULLREQUEST_PULLREQUESTNUMBER;
/**
- * This program should be invoked as `node ./scripts/update-experimental-branches [Branch2] [...]`
+ * This program should be invoked as `node ./scripts/update-experimental-branches [PR2] [...]`
+ * The order PR numbers are passed controls the order in which they are merged together.
+ * TODO: the following is racey - if two experiment-enlisted PRs trigger simultaneously and witness one another in an unupdated state, they'll both produce
+ * a new experimental branch, but each will be missing a change from the other. There's no _great_ way to fix this beyond setting the maximum concurrency
+ * of this task to 1 (so only one job is allowed to update experiments at a time).
*/
async function main() {
- const branchesRaw = process.argv[3];
- const branches = process.argv.slice(3);
- if (!branches.length) {
- throw new Error(`No experimental branches, aborting...`);
+ const prnums = process.argv.slice(3);
+ if (!prnums.length) {
+ return; // No enlisted PRs, nothing to update
}
- console.log(`Performing experimental branch updating and merging for branches ${branchesRaw}`);
+ if (!prnums.some(n => n === triggeredPR)) {
+ return; // Only have work to do for enlisted PRs
+ }
+ console.log(`Performing experimental branch updating and merging for pull requests ${prnums.join(", ")}`);
+
+ const userName = process.env.GH_USERNAME;
+ const remoteUrl = `https://${process.argv[2]}@github.com/${userName}/TypeScript.git`;
- const gh = new Octokit();
- gh.authenticate({
- type: "token",
- token: process.argv[2]
- });
-
- // Fetch all relevant refs
- runSequence([
- ["git", ["fetch", "origin", "master:master", ...branches.map(b => `${b}:${b}`)]]
- ])
-
// Forcibly cleanup workspace
runSequence([
- ["git", ["clean", "-fdx"]],
["git", ["checkout", "."]],
+ ["git", ["fetch", "-fu", "origin", "master:master"]],
["git", ["checkout", "master"]],
+ ["git", ["remote", "add", "fork", remoteUrl]], // Add the remote fork
]);
-
- // Update branches
- for (const branch of branches) {
- // Checkout, then get the merge base
- const mergeBase = runSequence([
- ["git", ["checkout", branch]],
- ["git", ["merge-base", branch, "master"]],
- ]);
- // Simulate the merge and abort if there are conflicts
- const mergeTree = runSequence([
- ["git", ["merge-tree", mergeBase, branch, "master"]]
- ]);
- if (mergeTree.indexOf(`===${"="}===`)) { // 7 equals is the center of the merge conflict marker
- const res = await gh.pulls.list({owner: "Microsoft", repo: "TypeScript", base: branch});
- if (res && res.data && res.data[0]) {
- const pr = res.data[0];
- await gh.issues.createComment({
- owner: "Microsoft",
- repo: "TypeScript",
- number: pr.number,
- body: `This PR is configured as an experiment, and currently has merge conflicts with master - please rebase onto master and fix the conflicts.`
- });
+
+ const gh = new Octokit({
+ auth: process.argv[2]
+ });
+ for (const numRaw of prnums) {
+ const num = +numRaw;
+ if (num) {
+ // PR number rather than branch name - lookup info
+ const inputPR = await gh.pulls.get({ owner: "Microsoft", repo: "TypeScript", pull_number: num });
+ // GH calculates the rebaseable-ness of a PR into its target, so we can just use that here
+ if (!inputPR.data.rebaseable) {
+ if (+triggeredPR === num) {
+ await gh.issues.createComment({
+ owner: "Microsoft",
+ repo: "TypeScript",
+ issue_number: num,
+ body: `This PR is configured as an experiment, and currently has rebase conflicts with master - please rebase onto master and fix the conflicts.`
+ });
+ throw new Error(`Rebase conflict detected in PR ${num} with master`);
+ }
+ return; // A PR is currently in conflict, give up
}
- throw new Error(`Merge conflict detected on branch ${branch} with master`);
+ runSequence([
+ ["git", ["fetch", "origin", `pull/${num}/head:${num}`]],
+ ["git", ["checkout", `${num}`]],
+ ["git", ["rebase", "master"]],
+ ["git", ["push", "-f", "-u", "fork", `${num}`]], // Keep a rebased copy of this branch in our fork
+ ]);
+
+ }
+ else {
+ throw new Error(`Invalid PR number: ${numRaw}`);
}
- // Merge is good - apply a rebase and (force) push
- runSequence([
- ["git", ["rebase", "master"]],
- ["git", ["push", "-f", "-u", "origin", branch]],
- ]);
}
// Return to `master` and make a new `experimental` branch
runSequence([
["git", ["checkout", "master"]],
- ["git", ["branch", "-D", "experimental"]],
["git", ["checkout", "-b", "experimental"]],
]);
// Merge each branch into `experimental` (which, if there is a conflict, we now know is from inter-experiment conflict)
- for (const branch of branches) {
+ for (const branch of prnums) {
// Find the merge base
const mergeBase = runSequence([
["git", ["merge-base", branch, "experimental"]],
]);
// Simulate the merge and abort if there are conflicts
const mergeTree = runSequence([
- ["git", ["merge-tree", mergeBase, branch, "experimental"]]
+ ["git", ["merge-tree", mergeBase.trim(), branch, "experimental"]]
]);
- if (mergeTree.indexOf(`===${"="}===`)) { // 7 equals is the center of the merge conflict marker
- throw new Error(`Merge conflict detected on branch ${branch} with other experiment`);
+ if (mergeTree.indexOf(`===${"="}===`) >= 0) { // 7 equals is the center of the merge conflict marker
+ throw new Error(`Merge conflict detected involving PR ${branch} with other experiment`);
}
// Merge (always producing a merge commit)
runSequence([
@@ -90,7 +94,7 @@ async function main() {
}
// Every branch merged OK, force push the replacement `experimental` branch
runSequence([
- ["git", ["push", "-f", "-u", "origin", "experimental"]],
+ ["git", ["push", "-f", "-u", "fork", "experimental"]],
]);
}
diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts
index 4540a4409d6..660b19a5872 100644
--- a/src/compiler/binder.ts
+++ b/src/compiler/binder.ts
@@ -3229,8 +3229,7 @@ namespace ts {
// A ClassDeclaration is ES6 syntax.
transformFlags = subtreeFlags | TransformFlags.AssertES2015;
- // A class with a parameter property assignment, property initializer, computed property name, or decorator is
- // TypeScript syntax.
+ // A class with a parameter property assignment or decorator is TypeScript syntax.
// An exported declaration may be TypeScript syntax, but is handled by the visitor
// for a namespace declaration.
if ((subtreeFlags & TransformFlags.ContainsTypeScriptClassSyntax)
@@ -3247,8 +3246,7 @@ namespace ts {
// A ClassExpression is ES6 syntax.
let transformFlags = subtreeFlags | TransformFlags.AssertES2015;
- // A class with a parameter property assignment, property initializer, or decorator is
- // TypeScript syntax.
+ // A class with a parameter property assignment or decorator is TypeScript syntax.
if (subtreeFlags & TransformFlags.ContainsTypeScriptClassSyntax
|| node.typeParameters) {
transformFlags |= TransformFlags.AssertTypeScript;
@@ -3338,7 +3336,6 @@ namespace ts {
|| hasModifier(node, ModifierFlags.TypeScriptModifier)
|| node.typeParameters
|| node.type
- || (node.name && isComputedPropertyName(node.name)) // While computed method names aren't typescript, the TS transform must visit them to emit property declarations correctly
|| !node.body) {
transformFlags |= TransformFlags.AssertTypeScript;
}
@@ -3369,7 +3366,6 @@ namespace ts {
if (node.decorators
|| hasModifier(node, ModifierFlags.TypeScriptModifier)
|| node.type
- || (node.name && isComputedPropertyName(node.name)) // While computed accessor names aren't typescript, the TS transform must visit them to emit property declarations correctly
|| !node.body) {
transformFlags |= TransformFlags.AssertTypeScript;
}
@@ -3384,12 +3380,15 @@ namespace ts {
}
function computePropertyDeclaration(node: PropertyDeclaration, subtreeFlags: TransformFlags) {
- // A PropertyDeclaration is TypeScript syntax.
- let transformFlags = subtreeFlags | TransformFlags.AssertTypeScript;
+ let transformFlags = subtreeFlags | TransformFlags.ContainsClassFields;
- // If the PropertyDeclaration has an initializer or a computed name, we need to inform its ancestor
- // so that it handle the transformation.
- if (node.initializer || isComputedPropertyName(node.name)) {
+ // Decorators, TypeScript-specific modifiers, and type annotations are TypeScript syntax.
+ if (some(node.decorators) || hasModifier(node, ModifierFlags.TypeScriptModifier) || node.type) {
+ transformFlags |= TransformFlags.AssertTypeScript;
+ }
+
+ // Hoisted variables related to class properties should live within the TypeScript class wrapper.
+ if (isComputedPropertyName(node.name) || (hasStaticModifier(node) && node.initializer)) {
transformFlags |= TransformFlags.ContainsTypeScriptClassSyntax;
}
diff --git a/src/compiler/builder.ts b/src/compiler/builder.ts
index 78058bf5a53..d4c2132d36b 100644
--- a/src/compiler/builder.ts
+++ b/src/compiler/builder.ts
@@ -796,6 +796,7 @@ namespace ts {
(result as SemanticDiagnosticsBuilderProgram).getSemanticDiagnosticsOfNextAffectedFile = getSemanticDiagnosticsOfNextAffectedFile;
}
else if (kind === BuilderProgramKind.EmitAndSemanticDiagnosticsBuilderProgram) {
+ (result as EmitAndSemanticDiagnosticsBuilderProgram).getSemanticDiagnosticsOfNextAffectedFile = getSemanticDiagnosticsOfNextAffectedFile;
(result as EmitAndSemanticDiagnosticsBuilderProgram).emitNextAffectedFile = emitNextAffectedFile;
}
else {
@@ -913,6 +914,11 @@ namespace ts {
);
}
+ // Add file to affected file pending emit to handle for later emit time
+ if (kind === BuilderProgramKind.EmitAndSemanticDiagnosticsBuilderProgram) {
+ addToAffectedFilesPendingEmit(state, [(affected as SourceFile).path]);
+ }
+
// Get diagnostics for the affected file if its not ignored
if (ignoreSourceFile && ignoreSourceFile(affected as SourceFile)) {
// Get next affected file
@@ -951,18 +957,8 @@ namespace ts {
// When semantic builder asks for diagnostics of the whole program,
// ensure that all the affected files are handled
- let affected: SourceFile | Program | undefined;
- let affectedFilesPendingEmit: Path[] | undefined;
- while (affected = getNextAffectedFile(state, cancellationToken, computeHash)) {
- if (affected !== state.program && kind === BuilderProgramKind.EmitAndSemanticDiagnosticsBuilderProgram) {
- (affectedFilesPendingEmit || (affectedFilesPendingEmit = [])).push((affected as SourceFile).path);
- }
- doneWithAffectedFile(state, affected);
- }
-
- // In case of emit builder, cache the files to be emitted
- if (affectedFilesPendingEmit) {
- addToAffectedFilesPendingEmit(state, affectedFilesPendingEmit);
+ // tslint:disable-next-line no-empty
+ while (getSemanticDiagnosticsOfNextAffectedFile(cancellationToken)) {
}
let diagnostics: Diagnostic[] | undefined;
@@ -997,7 +993,7 @@ namespace ts {
return map;
}
- export function createBuildProgramUsingProgramBuildInfo(program: ProgramBuildInfo): EmitAndSemanticDiagnosticsBuilderProgram & SemanticDiagnosticsBuilderProgram {
+ export function createBuildProgramUsingProgramBuildInfo(program: ProgramBuildInfo): EmitAndSemanticDiagnosticsBuilderProgram {
const fileInfos = createMapFromTemplate(program.fileInfos);
const state: ReusableBuilderProgramState = {
fileInfos,
@@ -1181,7 +1177,7 @@ namespace ts {
* The builder that can handle the changes in program and iterate through changed file to emit the files
* The semantic diagnostics are cached per file and managed by clearing for the changed/affected files
*/
- export interface EmitAndSemanticDiagnosticsBuilderProgram extends BuilderProgram {
+ export interface EmitAndSemanticDiagnosticsBuilderProgram extends SemanticDiagnosticsBuilderProgram {
/**
* Emits the next affected file's emit result (EmitResult and sourceFiles emitted) or returns undefined if iteration is complete
* The first of writeFile if provided, writeFile of BuilderProgramHost if provided, writeFile of compiler host
diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts
index c221da7bdb2..817364ba832 100644
--- a/src/compiler/checker.ts
+++ b/src/compiler/checker.ts
@@ -182,6 +182,10 @@ namespace ts {
node = getParseTreeNode(node);
return node ? getTypeOfNode(node) : errorType;
},
+ getTypeOfAssignmentPattern: nodeIn => {
+ const node = getParseTreeNode(nodeIn, isAssignmentPattern);
+ return node && getTypeOfAssignmentPattern(node) || errorType;
+ },
getPropertySymbolOfDestructuringAssignment: locationIn => {
const location = getParseTreeNode(locationIn, isIdentifier);
return location ? getPropertySymbolOfDestructuringAssignment(location) : undefined;
@@ -5227,7 +5231,7 @@ namespace ts {
}
}
// Use contextual parameter type if one is available
- const type = declaration.symbol.escapedName === InternalSymbolName.This ? getContextualThisParameterType(func) : getContextuallyTypedParameterType(declaration);
+ const type = declaration.symbol.escapedName === InternalSymbolName.This ? getContextualThisParameterType(func) : getContextuallyTypedParameterType(declaration, /*forCache*/ true);
if (type) {
return addOptionality(type, isOptional);
}
@@ -5917,7 +5921,20 @@ namespace ts {
return anyType;
}
+ function getTypeOfSymbolWithDeferredType(symbol: Symbol) {
+ const links = getSymbolLinks(symbol);
+ if (!links.type) {
+ Debug.assertDefined(links.deferralParent);
+ Debug.assertDefined(links.deferralConstituents);
+ links.type = links.deferralParent!.flags & TypeFlags.Union ? getUnionType(links.deferralConstituents!) : getIntersectionType(links.deferralConstituents!);
+ }
+ return links.type;
+ }
+
function getTypeOfSymbol(symbol: Symbol): Type {
+ if (getCheckFlags(symbol) & CheckFlags.DeferredType) {
+ return getTypeOfSymbolWithDeferredType(symbol);
+ }
if (getCheckFlags(symbol) & CheckFlags.Instantiated) {
return getTypeOfInstantiatedSymbol(symbol);
}
@@ -7052,10 +7069,10 @@ namespace ts {
// Union the result types when more than one signature matches
if (unionSignatures.length > 1) {
let thisParameter = signature.thisParameter;
- if (forEach(unionSignatures, sig => sig.thisParameter)) {
- // TODO: GH#18217 We tested that *some* has thisParameter and now act as if *all* do
+ const firstThisParameterOfUnionSignatures = forEach(unionSignatures, sig => sig.thisParameter);
+ if (firstThisParameterOfUnionSignatures) {
const thisType = getUnionType(map(unionSignatures, sig => sig.thisParameter ? getTypeOfSymbol(sig.thisParameter) : anyType), UnionReduction.Subtype);
- thisParameter = createSymbolWithType(signature.thisParameter!, thisType);
+ thisParameter = createSymbolWithType(firstThisParameterOfUnionSignatures, thisType);
}
s = createUnionSignature(signature, unionSignatures);
s.thisParameter = thisParameter;
@@ -7299,7 +7316,8 @@ namespace ts {
stringIndexInfo = createIndexInfo(anyType, /*isReadonly*/ false);
}
}
- const numberIndexInfo = symbol.flags & SymbolFlags.Enum ? enumNumberIndexInfo : undefined;
+ const numberIndexInfo = symbol.flags & SymbolFlags.Enum && (getDeclaredTypeOfSymbol(symbol).flags & TypeFlags.Enum ||
+ some(type.properties, prop => !!(getTypeOfSymbol(prop).flags & TypeFlags.NumberLike))) ? enumNumberIndexInfo : undefined;
setStructuredTypeMembers(type, members, emptyArray, emptyArray, stringIndexInfo, numberIndexInfo);
// We resolve the members before computing the signatures because a signature may use
// typeof with a qualified name expression that circularly references the type we are
@@ -7999,10 +8017,13 @@ namespace ts {
else if (isUnion) {
const indexInfo = !isLateBoundName(name) && (isNumericLiteralName(name) && getIndexInfoOfType(type, IndexKind.Number) || getIndexInfoOfType(type, IndexKind.String));
if (indexInfo) {
- checkFlags |= indexInfo.isReadonly ? CheckFlags.Readonly : 0;
- checkFlags |= CheckFlags.WritePartial;
+ checkFlags |= CheckFlags.WritePartial | (indexInfo.isReadonly ? CheckFlags.Readonly : 0);
indexTypes = append(indexTypes, isTupleType(type) ? getRestTypeOfTupleType(type) || undefinedType : indexInfo.type);
}
+ else if (isObjectLiteralType(type)) {
+ checkFlags |= CheckFlags.WritePartial;
+ indexTypes = append(indexTypes, undefinedType);
+ }
else {
checkFlags |= CheckFlags.ReadPartial;
}
@@ -8057,7 +8078,15 @@ namespace ts {
result.declarations = declarations!;
result.nameType = nameType;
- result.type = isUnion ? getUnionType(propTypes) : getIntersectionType(propTypes);
+ if (propTypes.length > 2) {
+ // When `propTypes` has the potential to explode in size when normalized, defer normalization until absolutely needed
+ result.checkFlags |= CheckFlags.DeferredType;
+ result.deferralParent = containingType;
+ result.deferralConstituents = propTypes;
+ }
+ else {
+ result.type = isUnion ? getUnionType(propTypes) : getIntersectionType(propTypes);
+ }
return result;
}
@@ -8166,6 +8195,9 @@ namespace ts {
propTypes.push(getTypeOfSymbol(prop));
}
}
+ if (kind === IndexKind.String) {
+ append(propTypes, getIndexTypeOfType(type, IndexKind.Number));
+ }
if (propTypes.length) {
return getUnionType(propTypes, UnionReduction.Subtype);
}
@@ -9908,6 +9940,12 @@ namespace ts {
else {
// We are attempting to construct a type of the form X & (A | B) & Y. Transform this into a type of
// the form X & A & Y | X & B & Y and recursively reduce until no union type constituents remain.
+ // If the estimated size of the resulting union type exceeds 100000 constituents, report an error.
+ const size = reduceLeft(typeSet, (n, t) => n * (t.flags & TypeFlags.Union ? (t).types.length : 1), 1);
+ if (size >= 100000) {
+ error(currentNode, Diagnostics.Expression_produces_a_union_type_that_is_too_complex_to_represent);
+ return errorType;
+ }
const unionIndex = findIndex(typeSet, t => (t.flags & TypeFlags.Union) !== 0);
const unionType = typeSet[unionIndex];
result = getUnionType(map(unionType.types, t => getIntersectionType(replaceElement(typeSet, unionIndex, t))),
@@ -10067,7 +10105,7 @@ namespace ts {
return false;
}
- function getPropertyTypeForIndexType(originalObjectType: Type, objectType: Type, indexType: Type, accessNode: ElementAccessExpression | IndexedAccessTypeNode | PropertyName | BindingName | SyntheticExpression | undefined, accessFlags: AccessFlags) {
+ function getPropertyTypeForIndexType(originalObjectType: Type, objectType: Type, indexType: Type, fullIndexType: Type, suppressNoImplicitAnyError: boolean, accessNode: ElementAccessExpression | IndexedAccessTypeNode | PropertyName | BindingName | SyntheticExpression | undefined, accessFlags: AccessFlags) {
const accessExpression = accessNode && accessNode.kind === SyntaxKind.ElementAccessExpression ? accessNode : undefined;
const propName = isTypeUsableAsPropertyName(indexType) ?
getPropertyNameFromType(indexType) :
@@ -10141,7 +10179,7 @@ namespace ts {
if (objectType.symbol === globalThisSymbol && propName !== undefined && globalThisSymbol.exports!.has(propName) && (globalThisSymbol.exports!.get(propName)!.flags & SymbolFlags.BlockScoped)) {
error(accessExpression, Diagnostics.Property_0_does_not_exist_on_type_1, unescapeLeadingUnderscores(propName), typeToString(objectType));
}
- else if (noImplicitAny && !compilerOptions.suppressImplicitAnyIndexErrors) {
+ else if (noImplicitAny && !compilerOptions.suppressImplicitAnyIndexErrors && !suppressNoImplicitAnyError) {
if (propName !== undefined && typeHasStaticProperty(propName, objectType)) {
error(accessExpression, Diagnostics.Property_0_is_a_static_member_of_type_1, propName as string, typeToString(objectType));
}
@@ -10161,7 +10199,29 @@ namespace ts {
error(accessExpression, Diagnostics.Element_implicitly_has_an_any_type_because_type_0_has_no_index_signature_Did_you_mean_to_call_1, typeToString(objectType), suggestion);
}
else {
- error(accessExpression, Diagnostics.Element_implicitly_has_an_any_type_because_type_0_has_no_index_signature, typeToString(objectType));
+ let errorInfo: DiagnosticMessageChain | undefined;
+ if (indexType.flags & TypeFlags.EnumLiteral) {
+ errorInfo = chainDiagnosticMessages(/* details */ undefined, Diagnostics.Property_0_does_not_exist_on_type_1, "[" + typeToString(indexType) + "]", typeToString(objectType));
+ }
+ else if (indexType.flags & TypeFlags.UniqueESSymbol) {
+ const symbolName = getFullyQualifiedName((indexType as UniqueESSymbolType).symbol, accessExpression);
+ errorInfo = chainDiagnosticMessages(/* details */ undefined, Diagnostics.Property_0_does_not_exist_on_type_1, "[" + symbolName + "]", typeToString(objectType));
+ }
+ else if (indexType.flags & TypeFlags.StringLiteral) {
+ errorInfo = chainDiagnosticMessages(/* details */ undefined, Diagnostics.Property_0_does_not_exist_on_type_1, (indexType as StringLiteralType).value, typeToString(objectType));
+ }
+ else if (indexType.flags & TypeFlags.NumberLiteral) {
+ errorInfo = chainDiagnosticMessages(/* details */ undefined, Diagnostics.Property_0_does_not_exist_on_type_1, (indexType as NumberLiteralType).value, typeToString(objectType));
+ }
+ else if (indexType.flags & (TypeFlags.Number | TypeFlags.String)) {
+ errorInfo = chainDiagnosticMessages(/* details */ undefined, Diagnostics.No_index_signature_with_a_parameter_of_type_0_was_found_on_type_1, typeToString(indexType), typeToString(objectType));
+ }
+
+ errorInfo = chainDiagnosticMessages(
+ errorInfo,
+ Diagnostics.Element_implicitly_has_an_any_type_because_expression_of_type_0_can_t_be_used_to_index_type_1, typeToString(fullIndexType), typeToString(objectType)
+ );
+ diagnostics.add(createDiagnosticForNodeFromMessageChain(accessExpression, errorInfo));
}
}
}
@@ -10360,7 +10420,7 @@ namespace ts {
const propTypes: Type[] = [];
let wasMissingProp = false;
for (const t of (indexType).types) {
- const propType = getPropertyTypeForIndexType(objectType, apparentObjectType, t, accessNode, accessFlags);
+ const propType = getPropertyTypeForIndexType(objectType, apparentObjectType, t, indexType, wasMissingProp, accessNode, accessFlags);
if (propType) {
propTypes.push(propType);
}
@@ -10378,7 +10438,7 @@ namespace ts {
}
return accessFlags & AccessFlags.Writing ? getIntersectionType(propTypes) : getUnionType(propTypes);
}
- return getPropertyTypeForIndexType(objectType, apparentObjectType, indexType, accessNode, accessFlags | AccessFlags.CacheSymbol);
+ return getPropertyTypeForIndexType(objectType, apparentObjectType, indexType, indexType, /* supressNoImplicitAnyError */ false, accessNode, accessFlags | AccessFlags.CacheSymbol);
}
function getTypeFromIndexedAccessTypeNode(node: IndexedAccessTypeNode) {
@@ -11462,6 +11522,7 @@ namespace ts {
case SyntaxKind.FunctionExpression:
case SyntaxKind.ArrowFunction:
case SyntaxKind.MethodDeclaration:
+ case SyntaxKind.FunctionDeclaration: // Function declarations can have context when annotated with a jsdoc @type
return isContextSensitiveFunctionLikeDeclaration(node);
case SyntaxKind.ObjectLiteralExpression:
return some((node).properties, isContextSensitive);
@@ -11495,6 +11556,9 @@ namespace ts {
}
function isContextSensitiveFunctionLikeDeclaration(node: FunctionLikeDeclaration): boolean {
+ if (isFunctionDeclaration(node) && (!isInJSFile(node) || !getTypeForDeclarationFromJSDocComment(node))) {
+ return false;
+ }
// Functions with type parameters are not context sensitive.
if (node.typeParameters) {
return false;
@@ -12281,8 +12345,8 @@ namespace ts {
return false;
}
- function isIgnoredJsxProperty(source: Type, sourceProp: Symbol, targetMemberType: Type | undefined) {
- return getObjectFlags(source) & ObjectFlags.JsxAttributes && !(isUnhyphenatedJsxName(sourceProp.escapedName) || targetMemberType);
+ function isIgnoredJsxProperty(source: Type, sourceProp: Symbol) {
+ return getObjectFlags(source) & ObjectFlags.JsxAttributes && !isUnhyphenatedJsxName(sourceProp.escapedName);
}
/**
@@ -12928,6 +12992,18 @@ namespace ts {
return result;
}
+ function propagateSidebandVarianceFlags(typeArguments: readonly Type[], variances: VarianceFlags[]) {
+ for (let i = 0; i < variances.length; i++) {
+ const v = variances[i];
+ if (v & VarianceFlags.Unmeasurable) {
+ instantiateType(typeArguments[i], reportUnmeasurableMarkers);
+ }
+ if (v & VarianceFlags.Unreliable) {
+ instantiateType(typeArguments[i], reportUnreliableMarkers);
+ }
+ }
+ }
+
// Determine if possibly recursive types are related. First, check if the result is already available in the global cache.
// Second, check if we have already started a comparison of the given two types in which case we assume the result to be true.
// Third, check if both types are part of deeply nested chains of generic type instantiations and if so assume the types are
@@ -12945,6 +13021,16 @@ namespace ts {
// as a failure, and should be updated as a reported failure by the bottom of this function.
}
else {
+ if (outofbandVarianceMarkerHandler) {
+ // We're in the middle of variance checking - integrate any unmeasurable/unreliable flags from this cached component
+ if (source.flags & (TypeFlags.Object | TypeFlags.Conditional) && source.aliasSymbol &&
+ source.aliasTypeArguments && source.aliasSymbol === target.aliasSymbol) {
+ propagateSidebandVarianceFlags(source.aliasTypeArguments, getAliasVariances(source.aliasSymbol));
+ }
+ if (getObjectFlags(source) & ObjectFlags.Reference && getObjectFlags(target) & ObjectFlags.Reference && (source).target === (target).target && length((source).typeArguments)) {
+ propagateSidebandVarianceFlags((source).typeArguments!, getVariances((source).target));
+ }
+ }
return related === RelationComparisonResult.Succeeded ? Ternary.True : Ternary.False;
}
}
@@ -13463,6 +13549,49 @@ namespace ts {
return result || properties;
}
+ function isPropertySymbolTypeRelated(sourceProp: Symbol, targetProp: Symbol, getTypeOfSourceProperty: (sym: Symbol) => Type, reportErrors: boolean): Ternary {
+ const targetIsOptional = strictNullChecks && !!(getCheckFlags(targetProp) & CheckFlags.Partial);
+ const source = getTypeOfSourceProperty(sourceProp);
+ if (getCheckFlags(targetProp) & CheckFlags.DeferredType && !getSymbolLinks(targetProp).type) {
+ // Rather than resolving (and normalizing) the type, relate constituent-by-constituent without performing normalization or seconadary passes
+ const links = getSymbolLinks(targetProp);
+ Debug.assertDefined(links.deferralParent);
+ Debug.assertDefined(links.deferralConstituents);
+ const unionParent = !!(links.deferralParent!.flags & TypeFlags.Union);
+ let result = unionParent ? Ternary.False : Ternary.True;
+ const targetTypes = links.deferralConstituents!;
+ for (const targetType of targetTypes) {
+ const related = isRelatedTo(source, targetType, /*reportErrors*/ false, /*headMessage*/ undefined, /*isIntersectionConstituent*/ !unionParent);
+ if (!unionParent) {
+ if (!related) {
+ // Can't assign to a target individually - have to fallback to assigning to the _whole_ intersection (which forces normalization)
+ return isRelatedTo(source, addOptionality(getTypeOfSymbol(targetProp), targetIsOptional), reportErrors);
+ }
+ result &= related;
+ }
+ else {
+ if (related) {
+ return related;
+ }
+ }
+ }
+ if (unionParent && !result && targetIsOptional) {
+ result = isRelatedTo(source, undefinedType);
+ }
+ if (unionParent && !result && reportErrors) {
+ // The easiest way to get the right errors here is to un-defer (which may be costly)
+ // If it turns out this is too costly too often, we can replicate the error handling logic within
+ // typeRelatedToSomeType without the discriminatable type branch (as that requires a manifest union
+ // type on which to hand discriminable properties, which we are expressly trying to avoid here)
+ return isRelatedTo(source, addOptionality(getTypeOfSymbol(targetProp), targetIsOptional), reportErrors);
+ }
+ return result;
+ }
+ else {
+ return isRelatedTo(source, addOptionality(getTypeOfSymbol(targetProp), targetIsOptional), reportErrors);
+ }
+ }
+
function propertyRelatedTo(source: Type, target: Type, sourceProp: Symbol, targetProp: Symbol, getTypeOfSourceProperty: (sym: Symbol) => Type, reportErrors: boolean): Ternary {
const sourcePropFlags = getDeclarationModifierFlagsFromSymbol(sourceProp);
const targetPropFlags = getDeclarationModifierFlagsFromSymbol(targetProp);
@@ -13505,7 +13634,7 @@ namespace ts {
return Ternary.False;
}
// If the target comes from a partial union prop, allow `undefined` in the target type
- const related = isRelatedTo(getTypeOfSourceProperty(sourceProp), addOptionality(getTypeOfSymbol(targetProp), !!(getCheckFlags(targetProp) & CheckFlags.Partial)), reportErrors);
+ const related = isPropertySymbolTypeRelated(sourceProp, targetProp, getTypeOfSourceProperty, reportErrors);
if (!related) {
if (reportErrors) {
reportError(Diagnostics.Types_of_property_0_are_incompatible, symbolToString(targetProp));
@@ -13617,9 +13746,6 @@ namespace ts {
if (!(targetProp.flags & SymbolFlags.Prototype)) {
const sourceProp = getPropertyOfType(source, targetProp.escapedName);
if (sourceProp && sourceProp !== targetProp) {
- if (isIgnoredJsxProperty(source, sourceProp, getTypeOfSymbol(targetProp))) {
- continue;
- }
const related = propertyRelatedTo(source, target, sourceProp, targetProp, getTypeOfSymbol, reportErrors);
if (!related) {
return Ternary.False;
@@ -13765,7 +13891,7 @@ namespace ts {
function eachPropertyRelatedTo(source: Type, target: Type, kind: IndexKind, reportErrors: boolean): Ternary {
let result = Ternary.True;
for (const prop of getPropertiesOfObjectType(source)) {
- if (isIgnoredJsxProperty(source, prop, /*targetMemberType*/ undefined)) {
+ if (isIgnoredJsxProperty(source, prop)) {
continue;
}
// Skip over symbol-named members
@@ -13977,12 +14103,6 @@ namespace ts {
if (unreliable) {
variance |= VarianceFlags.Unreliable;
}
- const covariantID = getRelationKey(typeWithSub, typeWithSuper, assignableRelation);
- const contravariantID = getRelationKey(typeWithSuper, typeWithSub, assignableRelation);
- // We delete the results of these checks, as we want them to actually be run, see the `Unmeasurable` variance we cache,
- // And then fall back to a structural result.
- assignableRelation.delete(covariantID);
- assignableRelation.delete(contravariantID);
}
variances.push(variance);
}
@@ -14503,7 +14623,7 @@ namespace ts {
* with no call or construct signatures.
*/
function isObjectTypeWithInferableIndex(type: Type) {
- return type.symbol && (type.symbol.flags & (SymbolFlags.ObjectLiteral | SymbolFlags.TypeLiteral | SymbolFlags.ValueModule)) !== 0 &&
+ return type.symbol && (type.symbol.flags & (SymbolFlags.ObjectLiteral | SymbolFlags.TypeLiteral | SymbolFlags.Enum | SymbolFlags.ValueModule)) !== 0 &&
!typeHasCallOrConstructSignatures(type);
}
@@ -14647,26 +14767,34 @@ namespace ts {
function getWidenedTypeWithContext(type: Type, context: WideningContext | undefined): Type {
if (getObjectFlags(type) & ObjectFlags.RequiresWidening) {
+ if (context === undefined && type.widened) {
+ return type.widened;
+ }
+ let result: Type | undefined;
if (type.flags & TypeFlags.Nullable) {
- return anyType;
+ result = anyType;
}
- if (isObjectLiteralType(type)) {
- return getWidenedTypeOfObjectLiteral(type, context);
+ else if (isObjectLiteralType(type)) {
+ result = getWidenedTypeOfObjectLiteral(type, context);
}
- if (type.flags & TypeFlags.Union) {
+ else if (type.flags & TypeFlags.Union) {
const unionContext = context || createWideningContext(/*parent*/ undefined, /*propertyName*/ undefined, (type).types);
const widenedTypes = sameMap((type).types, t => t.flags & TypeFlags.Nullable ? t : getWidenedTypeWithContext(t, unionContext));
// Widening an empty object literal transitions from a highly restrictive type to
// a highly inclusive one. For that reason we perform subtype reduction here if the
// union includes empty object types (e.g. reducing {} | string to just {}).
- return getUnionType(widenedTypes, some(widenedTypes, isEmptyObjectType) ? UnionReduction.Subtype : UnionReduction.Literal);
+ result = getUnionType(widenedTypes, some(widenedTypes, isEmptyObjectType) ? UnionReduction.Subtype : UnionReduction.Literal);
}
- if (type.flags & TypeFlags.Intersection) {
- return getIntersectionType(sameMap((type).types, getWidenedType));
+ else if (type.flags & TypeFlags.Intersection) {
+ result = getIntersectionType(sameMap((type).types, getWidenedType));
}
- if (isArrayType(type) || isTupleType(type)) {
- return createTypeReference((type).target, sameMap((type).typeArguments, getWidenedType));
+ else if (isArrayType(type) || isTupleType(type)) {
+ result = createTypeReference((type).target, sameMap((type).typeArguments, getWidenedType));
}
+ if (result && context === undefined) {
+ type.widened = result;
+ }
+ return result || type;
}
return type;
}
@@ -14832,13 +14960,6 @@ namespace ts {
return context && createInferenceContextWorker(map(context.inferences, cloneInferenceInfo), context.signature, context.flags | extraFlags, context.compareTypes);
}
- function cloneInferredPartOfContext(context: InferenceContext): InferenceContext | undefined {
- const inferences = filter(context.inferences, hasInferenceCandidates);
- return inferences.length ?
- createInferenceContextWorker(map(inferences, cloneInferenceInfo), context.signature, context.flags, context.compareTypes) :
- undefined;
- }
-
function createInferenceContextWorker(inferences: InferenceInfo[], signature: Signature | undefined, flags: InferenceFlags, compareTypes: TypeComparer): InferenceContext {
const context: InferenceContext = {
inferences,
@@ -15070,11 +15191,7 @@ namespace ts {
if (source.aliasSymbol && source.aliasTypeArguments && source.aliasSymbol === target.aliasSymbol) {
// Source and target are types originating in the same generic type alias declaration.
// Simply infer from source type arguments to target type arguments.
- const sourceTypes = source.aliasTypeArguments;
- const targetTypes = target.aliasTypeArguments!;
- for (let i = 0; i < sourceTypes.length; i++) {
- inferFromTypes(sourceTypes[i], targetTypes[i]);
- }
+ inferFromTypeArguments(source.aliasTypeArguments, target.aliasTypeArguments!, getAliasVariances(source.aliasSymbol));
return;
}
if (source.flags & TypeFlags.Union && target.flags & TypeFlags.Union && !(source.flags & TypeFlags.EnumLiteral && target.flags & TypeFlags.EnumLiteral) ||
@@ -15180,18 +15297,7 @@ namespace ts {
}
if (getObjectFlags(source) & ObjectFlags.Reference && getObjectFlags(target) & ObjectFlags.Reference && (source).target === (target).target) {
// If source and target are references to the same generic type, infer from type arguments
- const sourceTypes = (source).typeArguments || emptyArray;
- const targetTypes = (target).typeArguments || emptyArray;
- const count = sourceTypes.length < targetTypes.length ? sourceTypes.length : targetTypes.length;
- const variances = getVariances((source).target);
- for (let i = 0; i < count; i++) {
- if (i < variances.length && (variances[i] & VarianceFlags.VarianceMask) === VarianceFlags.Contravariant) {
- inferFromContravariantTypes(sourceTypes[i], targetTypes[i]);
- }
- else {
- inferFromTypes(sourceTypes[i], targetTypes[i]);
- }
- }
+ inferFromTypeArguments((source).typeArguments || emptyArray, (target).typeArguments || emptyArray, getVariances((source).target));
}
else if (source.flags & TypeFlags.Index && target.flags & TypeFlags.Index) {
contravariant = !contravariant;
@@ -15311,6 +15417,18 @@ namespace ts {
}
}
+ function inferFromTypeArguments(sourceTypes: readonly Type[], targetTypes: readonly Type[], variances: readonly VarianceFlags[]) {
+ const count = sourceTypes.length < targetTypes.length ? sourceTypes.length : targetTypes.length;
+ for (let i = 0; i < count; i++) {
+ if (i < variances.length && (variances[i] & VarianceFlags.VarianceMask) === VarianceFlags.Contravariant) {
+ inferFromContravariantTypes(sourceTypes[i], targetTypes[i]);
+ }
+ else {
+ inferFromTypes(sourceTypes[i], targetTypes[i]);
+ }
+ }
+ }
+
function inferFromContravariantTypes(source: Type, target: Type) {
if (strictFunctionTypes || priority & InferencePriority.AlwaysStrict) {
contravariant = !contravariant;
@@ -18150,7 +18268,7 @@ namespace ts {
}
// Return contextual type of parameter or undefined if no contextual type is available
- function getContextuallyTypedParameterType(parameter: ParameterDeclaration): Type | undefined {
+ function getContextuallyTypedParameterType(parameter: ParameterDeclaration, forCache: boolean): Type | undefined {
const func = parameter.parent;
if (!isContextSensitiveFunctionOrObjectLiteralMethod(func)) {
return undefined;
@@ -18171,8 +18289,21 @@ namespace ts {
links.resolvedSignature = cached;
return type;
}
- const contextualSignature = getContextualSignature(func);
+ let contextualSignature = getContextualSignature(func);
if (contextualSignature) {
+ if (forCache) {
+ // Calling the below guarantees the types are primed and assigned in the same way
+ // as when the parameter is reached via `checkFunctionExpressionOrObjectLiteralMethod`.
+ // This should prevent any uninstantiated inference variables in the contextual signature
+ // from leaking, and should lock in cached parameter types via `assignContextualParameterTypes`
+ // which we will then immediately use the results of below.
+ contextuallyCheckFunctionExpressionOrObjectLiteralMethod(func);
+ const type = getTypeOfSymbol(getMergedSymbol(func.symbol));
+ if (isTypeAny(type)) {
+ return type;
+ }
+ contextualSignature = getSignaturesOfType(type, SignatureKind.Call)[0];
+ }
const index = func.parameters.indexOf(parameter) - (getThisParameter(func) ? 1 : 0);
return parameter.dotDotDotToken && lastOrUndefined(func.parameters) === parameter ?
getRestTypeAtPosition(contextualSignature, index) :
@@ -18187,7 +18318,7 @@ namespace ts {
}
switch (declaration.kind) {
case SyntaxKind.Parameter:
- return getContextuallyTypedParameterType(declaration);
+ return getContextuallyTypedParameterType(declaration, /*forCache*/ false);
case SyntaxKind.BindingElement:
return getContextualTypeForBindingElement(declaration);
// By default, do nothing and return undefined - only parameters and binding elements have context implied by a parent
@@ -18344,11 +18475,13 @@ namespace ts {
}
return contextSensitive === true ? getTypeOfExpression(left) : contextSensitive;
case SyntaxKind.BarBarToken:
- // When an || expression has a contextual type, the operands are contextually typed by that type. When an ||
- // expression has no contextual type, the right operand is contextually typed by the type of the left operand,
- // except for the special case of Javascript declarations of the form `namespace.prop = namespace.prop || {}`
+ // When an || expression has a contextual type, the operands are contextually typed by that type, except
+ // when that type originates in a binding pattern, the right operand is contextually typed by the type of
+ // the left operand. When an || expression has no contextual type, the right operand is contextually typed
+ // by the type of the left operand, except for the special case of Javascript declarations of the form
+ // `namespace.prop = namespace.prop || {}`.
const type = getContextualType(binaryExpression, contextFlags);
- return !type && node === right && !isDefaultedExpandoInitializer(binaryExpression) ?
+ return node === right && (type && type.pattern || !type && !isDefaultedExpandoInitializer(binaryExpression)) ?
getTypeOfExpression(left) : type;
case SyntaxKind.AmpersandAmpersandToken:
case SyntaxKind.CommaToken:
@@ -20088,7 +20221,8 @@ namespace ts {
let propType: Type;
const leftType = checkNonNullExpression(left);
const parentSymbol = getNodeLinks(left).resolvedSymbol;
- const apparentType = getApparentType(getWidenedType(leftType));
+ // We widen array literals to get type any[] instead of undefined[] in non-strict mode
+ const apparentType = getApparentType(isEmptyArrayLiteralType(leftType) ? getWidenedType(leftType) : leftType);
if (isTypeAny(apparentType) || apparentType === silentNeverType) {
if (isIdentifier(left) && parentSymbol) {
markAliasReferenced(parentSymbol, node);
@@ -20101,7 +20235,7 @@ namespace ts {
markAliasReferenced(parentSymbol, node);
}
if (!prop) {
- const indexInfo = assignmentKind === AssignmentKind.None || !isGenericObjectType(leftType) ? getIndexInfoOfType(apparentType, IndexKind.String) : undefined;
+ const indexInfo = assignmentKind === AssignmentKind.None || !isGenericObjectType(leftType) || isThisTypeParameter(leftType) ? getIndexInfoOfType(apparentType, IndexKind.String) : undefined;
if (!(indexInfo && indexInfo.type)) {
if (isJSLiteralType(leftType)) {
return anyType;
@@ -20814,7 +20948,8 @@ namespace ts {
// We clone the inference context to avoid disturbing a resolution in progress for an
// outer call expression. Effectively we just want a snapshot of whatever has been
// inferred for any outer call expression so far.
- const outerMapper = getMapperFromContext(cloneInferenceContext(getInferenceContext(node), InferenceFlags.NoDefault));
+ const outerContext = getInferenceContext(node);
+ const outerMapper = getMapperFromContext(cloneInferenceContext(outerContext, InferenceFlags.NoDefault));
const instantiatedType = instantiateType(contextualType, outerMapper);
// If the contextual type is a generic function type with a single call signature, we
// instantiate the type with its own type parameters and type arguments. This ensures that
@@ -20831,8 +20966,13 @@ namespace ts {
// Inferences made from return types have lower priority than all other inferences.
inferTypes(context.inferences, inferenceSourceType, inferenceTargetType, InferencePriority.ReturnType);
// Create a type mapper for instantiating generic contextual types using the inferences made
- // from the return type.
- context.returnMapper = getMapperFromContext(cloneInferredPartOfContext(context));
+ // from the return type. We need a separate inference pass here because (a) instantiation of
+ // the source type uses the outer context's return mapper (which excludes inferences made from
+ // outer arguments), and (b) we don't want any further inferences going into this context.
+ const returnContext = createInferenceContext(signature.typeParameters!, signature, context.flags);
+ const returnSourceType = instantiateType(contextualType, outerContext && outerContext.returnMapper);
+ inferTypes(returnContext.inferences, returnSourceType, inferenceTargetType);
+ context.returnMapper = some(returnContext.inferences, hasInferenceCandidates) ? getMapperFromContext(returnContext) : undefined;
}
}
@@ -22956,12 +23096,18 @@ namespace ts {
checkGrammarForGenerator(node);
}
- const links = getNodeLinks(node);
const type = getTypeOfSymbol(getMergedSymbol(node.symbol));
if (isTypeAny(type)) {
return type;
}
+ contextuallyCheckFunctionExpressionOrObjectLiteralMethod(node, checkMode);
+
+ return type;
+ }
+
+ function contextuallyCheckFunctionExpressionOrObjectLiteralMethod(node: FunctionExpression | ArrowFunction | MethodDeclaration, checkMode?: CheckMode) {
+ const links = getNodeLinks(node);
// Check if function expression is contextually typed and assign parameter types if so.
if (!(links.flags & NodeCheckFlags.ContextChecked)) {
const contextualSignature = getContextualSignature(node);
@@ -22971,6 +23117,10 @@ namespace ts {
if (!(links.flags & NodeCheckFlags.ContextChecked)) {
links.flags |= NodeCheckFlags.ContextChecked;
if (contextualSignature) {
+ const type = getTypeOfSymbol(getMergedSymbol(node.symbol));
+ if (isTypeAny(type)) {
+ return;
+ }
const signature = getSignaturesOfType(type, SignatureKind.Call)[0];
if (isContextSensitive(node)) {
const inferenceContext = getInferenceContext(node);
@@ -22991,8 +23141,6 @@ namespace ts {
checkSignatureDeclaration(node);
}
}
-
- return type;
}
function getReturnOrPromisedType(node: FunctionLikeDeclaration | MethodSignature, functionFlags: FunctionFlags) {
@@ -29960,7 +30108,7 @@ namespace ts {
// }
// [ a ] from
// [a] = [ some array ...]
- function getTypeOfArrayLiteralOrObjectLiteralDestructuringAssignment(expr: Expression): Type {
+ function getTypeOfAssignmentPattern(expr: AssignmentPattern): Type | undefined {
Debug.assert(expr.kind === SyntaxKind.ObjectLiteralExpression || expr.kind === SyntaxKind.ArrayLiteralExpression);
// If this is from "for of"
// for ( { a } of elems) {
@@ -29979,17 +30127,16 @@ namespace ts {
// for ({ skills: { primary, secondary } } = multiRobot, i = 0; i < 1; i++) {
if (expr.parent.kind === SyntaxKind.PropertyAssignment) {
const node = cast(expr.parent.parent, isObjectLiteralExpression);
- const typeOfParentObjectLiteral = getTypeOfArrayLiteralOrObjectLiteralDestructuringAssignment(node);
+ const typeOfParentObjectLiteral = getTypeOfAssignmentPattern(node) || errorType;
const propertyIndex = indexOfNode(node.properties, expr.parent);
- return checkObjectLiteralDestructuringPropertyAssignment(node, typeOfParentObjectLiteral || errorType, propertyIndex)!; // TODO: GH#18217
+ return checkObjectLiteralDestructuringPropertyAssignment(node, typeOfParentObjectLiteral, propertyIndex);
}
// Array literal assignment - array destructuring pattern
- Debug.assert(expr.parent.kind === SyntaxKind.ArrayLiteralExpression);
+ const node = cast(expr.parent, isArrayLiteralExpression);
// [{ property1: p1, property2 }] = elems;
- const typeOfArrayLiteral = getTypeOfArrayLiteralOrObjectLiteralDestructuringAssignment(expr.parent);
- const elementType = checkIteratedTypeOrElementType(typeOfArrayLiteral || errorType, expr.parent, /*allowStringInput*/ false, /*allowAsyncIterables*/ false) || errorType;
- return checkArrayLiteralDestructuringElementAssignment(expr.parent, typeOfArrayLiteral,
- (expr.parent).elements.indexOf(expr), elementType || errorType)!; // TODO: GH#18217
+ const typeOfArrayLiteral = getTypeOfAssignmentPattern(node) || errorType;
+ const elementType = checkIteratedTypeOrElementType(typeOfArrayLiteral, expr.parent, /*allowStringInput*/ false, /*allowAsyncIterables*/ false) || errorType;
+ return checkArrayLiteralDestructuringElementAssignment(node, typeOfArrayLiteral, node.elements.indexOf(expr), elementType);
}
// Gets the property symbol corresponding to the property in destructuring assignment
@@ -30000,7 +30147,7 @@ namespace ts {
// [a] = [ property1, property2 ]
function getPropertySymbolOfDestructuringAssignment(location: Identifier) {
// Get the type of the object or array literal and then look for property of given name in the type
- const typeOfObjectLiteral = getTypeOfArrayLiteralOrObjectLiteralDestructuringAssignment(location.parent.parent);
+ const typeOfObjectLiteral = getTypeOfAssignmentPattern(cast(location.parent.parent, isAssignmentPattern));
return typeOfObjectLiteral && getPropertyOfType(typeOfObjectLiteral, location.escapedText);
}
diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts
index 3aaa217fbd0..394a94cb663 100644
--- a/src/compiler/commandLineParser.ts
+++ b/src/compiler/commandLineParser.ts
@@ -1022,8 +1022,7 @@ namespace ts {
}
}
- /* @internal */
- export interface OptionsBase {
+ interface OptionsBase {
[option: string]: CompilerOptionsValue | undefined;
}
diff --git a/src/compiler/core.ts b/src/compiler/core.ts
index 0034c275999..f1511b79c12 100644
--- a/src/compiler/core.ts
+++ b/src/compiler/core.ts
@@ -1,7 +1,7 @@
namespace ts {
// WARNING: The script `configureNightly.ts` uses a regexp to parse out these values.
// If changing the text in this section, be sure to test `configureNightly` too.
- export const versionMajorMinor = "3.5";
+ export const versionMajorMinor = "3.6";
/** The version of the TypeScript compiler release */
export const version = `${versionMajorMinor}.0-dev`;
}
diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json
index 101b584ca13..5d6dc7d6df4 100644
--- a/src/compiler/diagnosticMessages.json
+++ b/src/compiler/diagnosticMessages.json
@@ -3927,6 +3927,10 @@
"category": "Message",
"code": 6219
},
+ "'package.json' had a falsy '{0}' field.": {
+ "category": "Message",
+ "code": 6220
+ },
"Projects to reference": {
"category": "Message",
@@ -4288,6 +4292,14 @@
"category": "Error",
"code": 7052
},
+ "Element implicitly has an 'any' type because expression of type '{0}' can't be used to index type '{1}'.": {
+ "category": "Error",
+ "code": 7053
+ },
+ "No index signature with a parameter of type '{0}' was found on type '{1}'.": {
+ "category": "Error",
+ "code": 7054
+ },
"You cannot rename this element.": {
"category": "Error",
"code": 8000
diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts
index 07b1208688f..ddd273c3602 100644
--- a/src/compiler/emitter.ts
+++ b/src/compiler/emitter.ts
@@ -284,7 +284,6 @@ namespace ts {
// Write build information if applicable
if (!buildInfoPath || targetSourceFile || emitSkipped) return;
const program = host.getProgramBuildInfo();
- if (!bundle && !program) return;
if (host.isEmitBlocked(buildInfoPath) || compilerOptions.noEmit) {
emitSkipped = true;
return;
@@ -638,7 +637,12 @@ namespace ts {
}
/*@internal*/
- export function emitUsingBuildInfo(config: ParsedCommandLine, host: EmitUsingBuildInfoHost, getCommandLine: (ref: ProjectReference) => ParsedCommandLine | undefined): EmitUsingBuildInfoResult {
+ export function emitUsingBuildInfo(
+ config: ParsedCommandLine,
+ host: EmitUsingBuildInfoHost,
+ getCommandLine: (ref: ProjectReference) => ParsedCommandLine | undefined,
+ customTransformers?: CustomTransformers
+ ): EmitUsingBuildInfoResult {
const { buildInfoPath, jsFilePath, sourceMapFilePath, declarationFilePath, declarationMapPath } = getOutputPathsForBundle(config.options, /*forceDtsPaths*/ false);
const buildInfoText = host.readFile(Debug.assertDefined(buildInfoPath));
if (!buildInfoText) return buildInfoPath!;
@@ -723,7 +727,12 @@ namespace ts {
useCaseSensitiveFileNames: () => host.useCaseSensitiveFileNames(),
getProgramBuildInfo: returnUndefined
};
- emitFiles(notImplementedResolver, emitHost, /*targetSourceFile*/ undefined, getTransformers(config.options), /*emitOnlyDtsFiles*/ false);
+ emitFiles(
+ notImplementedResolver,
+ emitHost,
+ /*targetSourceFile*/ undefined,
+ getTransformers(config.options, customTransformers)
+ );
return outputFiles;
}
diff --git a/src/compiler/moduleNameResolver.ts b/src/compiler/moduleNameResolver.ts
index 19402b03ee9..604b82cce5e 100644
--- a/src/compiler/moduleNameResolver.ts
+++ b/src/compiler/moduleNameResolver.ts
@@ -141,7 +141,15 @@ namespace ts {
function readPackageJsonPathField(jsonContent: PackageJson, fieldName: K, baseDirectory: string, state: ModuleResolutionState): PackageJson[K] | undefined {
const fileName = readPackageJsonField(jsonContent, fieldName, "string", state);
- if (fileName === undefined) return;
+ if (fileName === undefined) {
+ return;
+ }
+ if (!fileName) {
+ if (state.traceEnabled) {
+ trace(state.host, Diagnostics.package_json_had_a_falsy_0_field, fieldName);
+ }
+ return;
+ }
const path = normalizePath(combinePaths(baseDirectory, fileName));
if (state.traceEnabled) {
trace(state.host, Diagnostics.package_json_has_0_field_1_that_references_2, fieldName, fileName, path);
diff --git a/src/compiler/moduleSpecifiers.ts b/src/compiler/moduleSpecifiers.ts
index 59bb74a22c1..bc448698c69 100644
--- a/src/compiler/moduleSpecifiers.ts
+++ b/src/compiler/moduleSpecifiers.ts
@@ -175,9 +175,9 @@ namespace ts.moduleSpecifiers {
function discoverProbableSymlinks(files: ReadonlyArray, getCanonicalFileName: GetCanonicalFileName, cwd: string): ReadonlyMap {
const result = createMap();
- const symlinks = mapDefined(files, sf =>
- sf.resolvedModules && firstDefinedIterator(sf.resolvedModules.values(), res =>
- res && res.originalPath && res.resolvedFileName !== res.originalPath ? [res.resolvedFileName, res.originalPath] : undefined));
+ const symlinks = flatten(mapDefined(files, sf =>
+ sf.resolvedModules && compact(arrayFrom(mapIterator(sf.resolvedModules.values(), res =>
+ res && res.originalPath && res.resolvedFileName !== res.originalPath ? [res.resolvedFileName, res.originalPath] as const : undefined)))));
for (const [resolvedPath, originalPath] of symlinks) {
const [commonResolved, commonOriginal] = guessDirectorySymlink(resolvedPath, originalPath, cwd, getCanonicalFileName);
result.set(commonOriginal, commonResolved);
diff --git a/src/compiler/resolutionCache.ts b/src/compiler/resolutionCache.ts
index b82a5a78d71..fb04d7b7a18 100644
--- a/src/compiler/resolutionCache.ts
+++ b/src/compiler/resolutionCache.ts
@@ -54,6 +54,7 @@ namespace ts {
writeLog(s: string): void;
maxNumberOfFilesToIterateForInvalidation?: number;
getCurrentProgram(): Program | undefined;
+ fileIsOpen(filePath: Path): boolean;
}
interface DirectoryWatchesOfFailedLookup {
@@ -698,6 +699,11 @@ namespace ts {
// If something to do with folder/file starting with "." in node_modules folder, skip it
if (isPathIgnored(fileOrDirectoryPath)) return false;
+ // prevent saving an open file from over-eagerly triggering invalidation
+ if (resolutionHost.fileIsOpen(fileOrDirectoryPath)) {
+ return false;
+ }
+
// Some file or directory in the watching directory is created
// Return early if it does not have any of the watching extension or not the custom failed lookup path
const dirOfFileOrDirectory = getDirectoryPath(fileOrDirectoryPath);
diff --git a/src/compiler/transformer.ts b/src/compiler/transformer.ts
index 1c1df9bb0c2..d520571e6df 100644
--- a/src/compiler/transformer.ts
+++ b/src/compiler/transformer.ts
@@ -44,6 +44,7 @@ namespace ts {
addRange(transformers, customTransformers && map(customTransformers.before, wrapScriptTransformerFactory));
transformers.push(transformTypeScript);
+ transformers.push(transformClassFields);
if (jsx === JsxEmit.React) {
transformers.push(transformJsx);
diff --git a/src/compiler/transformers/classFields.ts b/src/compiler/transformers/classFields.ts
new file mode 100644
index 00000000000..575e1d13346
--- /dev/null
+++ b/src/compiler/transformers/classFields.ts
@@ -0,0 +1,491 @@
+/*@internal*/
+namespace ts {
+ const enum ClassPropertySubstitutionFlags {
+ /**
+ * Enables substitutions for class expressions with static fields
+ * which have initializers that reference the class name.
+ */
+ ClassAliases = 1 << 0,
+ }
+ /**
+ * Transforms ECMAScript Class Syntax.
+ * TypeScript parameter property syntax is transformed in the TypeScript transformer.
+ * For now, this transforms public field declarations using TypeScript class semantics
+ * (where the declarations get elided and initializers are transformed as assignments in the constructor).
+ * Eventually, this transform will change to the ECMAScript semantics (with Object.defineProperty).
+ */
+ export function transformClassFields(context: TransformationContext) {
+ const {
+ hoistVariableDeclaration,
+ endLexicalEnvironment,
+ resumeLexicalEnvironment
+ } = context;
+ const resolver = context.getEmitResolver();
+
+ const previousOnSubstituteNode = context.onSubstituteNode;
+ context.onSubstituteNode = onSubstituteNode;
+
+ let enabledSubstitutions: ClassPropertySubstitutionFlags;
+
+ let classAliases: Identifier[];
+
+ /**
+ * Tracks what computed name expressions originating from elided names must be inlined
+ * at the next execution site, in document order
+ */
+ let pendingExpressions: Expression[] | undefined;
+
+ /**
+ * Tracks what computed name expression statements and static property initializers must be
+ * emitted at the next execution site, in document order (for decorated classes).
+ */
+ let pendingStatements: Statement[] | undefined;
+
+ return chainBundle(transformSourceFile);
+
+ function transformSourceFile(node: SourceFile) {
+ if (node.isDeclarationFile) {
+ return node;
+ }
+ const visited = visitEachChild(node, visitor, context);
+ addEmitHelpers(visited, context.readEmitHelpers());
+ return visited;
+ }
+
+ function visitor(node: Node): VisitResult {
+ if (!(node.transformFlags & TransformFlags.ContainsClassFields)) return node;
+
+ switch (node.kind) {
+ case SyntaxKind.ClassExpression:
+ return visitClassExpression(node as ClassExpression);
+ case SyntaxKind.ClassDeclaration:
+ return visitClassDeclaration(node as ClassDeclaration);
+ case SyntaxKind.VariableStatement:
+ return visitVariableStatement(node as VariableStatement);
+ }
+ return visitEachChild(node, visitor, context);
+ }
+
+ /**
+ * Visits the members of a class that has fields.
+ *
+ * @param node The node to visit.
+ */
+ function classElementVisitor(node: Node): VisitResult {
+ switch (node.kind) {
+ case SyntaxKind.Constructor:
+ // Constructors for classes using class fields are transformed in
+ // `visitClassDeclaration` or `visitClassExpression`.
+ return undefined;
+
+ case SyntaxKind.GetAccessor:
+ case SyntaxKind.SetAccessor:
+ case SyntaxKind.MethodDeclaration:
+ // Visit the name of the member (if it's a computed property name).
+ return visitEachChild(node, classElementVisitor, context);
+
+ case SyntaxKind.PropertyDeclaration:
+ return visitPropertyDeclaration(node as PropertyDeclaration);
+
+ case SyntaxKind.ComputedPropertyName:
+ return visitComputedPropertyName(node as ComputedPropertyName);
+
+ default:
+ return node;
+ }
+ }
+
+ function visitVariableStatement(node: VariableStatement) {
+ const savedPendingStatements = pendingStatements;
+ pendingStatements = [];
+
+ const visitedNode = visitEachChild(node, visitor, context);
+ const statement = some(pendingStatements) ?
+ [visitedNode, ...pendingStatements] :
+ visitedNode;
+
+ pendingStatements = savedPendingStatements;
+ return statement;
+ }
+
+ function visitComputedPropertyName(name: ComputedPropertyName) {
+ let node = visitEachChild(name, visitor, context);
+ if (some(pendingExpressions)) {
+ const expressions = pendingExpressions;
+ expressions.push(name.expression);
+ pendingExpressions = [];
+ node = updateComputedPropertyName(
+ node,
+ inlineExpressions(expressions)
+ );
+ }
+ return node;
+ }
+
+ function visitPropertyDeclaration(node: PropertyDeclaration) {
+ Debug.assert(!some(node.decorators));
+ // Create a temporary variable to store a computed property name (if necessary).
+ // If it's not inlineable, then we emit an expression after the class which assigns
+ // the property name to the temporary variable.
+ const expr = getPropertyNameExpressionIfNeeded(node.name, !!node.initializer);
+ if (expr && !isSimpleInlineableExpression(expr)) {
+ (pendingExpressions || (pendingExpressions = [])).push(expr);
+ }
+ return undefined;
+ }
+
+ function visitClassDeclaration(node: ClassDeclaration) {
+ if (!forEach(node.members, isPropertyDeclaration)) {
+ return visitEachChild(node, visitor, context);
+ }
+ const savedPendingExpressions = pendingExpressions;
+ pendingExpressions = undefined;
+
+ const extendsClauseElement = getEffectiveBaseTypeNode(node);
+ const isDerivedClass = !!(extendsClauseElement && skipOuterExpressions(extendsClauseElement.expression).kind !== SyntaxKind.NullKeyword);
+
+ const statements: Statement[] = [
+ updateClassDeclaration(
+ node,
+ node.decorators,
+ node.modifiers,
+ node.name,
+ node.typeParameters,
+ node.heritageClauses,
+ transformClassMembers(node, isDerivedClass)
+ )
+ ];
+
+ // Write any pending expressions from elided or moved computed property names
+ if (some(pendingExpressions)) {
+ statements.push(createExpressionStatement(inlineExpressions(pendingExpressions!)));
+ }
+ pendingExpressions = savedPendingExpressions;
+
+ // Emit static property assignment. Because classDeclaration is lexically evaluated,
+ // it is safe to emit static property assignment after classDeclaration
+ // From ES6 specification:
+ // HasLexicalDeclaration (N) : Determines if the argument identifier has a binding in this environment record that was created using
+ // a lexical declaration such as a LexicalDeclaration or a ClassDeclaration.
+ const staticProperties = getInitializedProperties(node, /*isStatic*/ true);
+ if (some(staticProperties)) {
+ addInitializedPropertyStatements(statements, staticProperties, getInternalName(node));
+ }
+
+ return statements;
+ }
+
+ function visitClassExpression(node: ClassExpression): Expression {
+ if (!forEach(node.members, isPropertyDeclaration)) {
+ return visitEachChild(node, visitor, context);
+ }
+ const savedPendingExpressions = pendingExpressions;
+ pendingExpressions = undefined;
+
+ // If this class expression is a transformation of a decorated class declaration,
+ // then we want to output the pendingExpressions as statements, not as inlined
+ // expressions with the class statement.
+ //
+ // In this case, we use pendingStatements to produce the same output as the
+ // class declaration transformation. The VariableStatement visitor will insert
+ // these statements after the class expression variable statement.
+ const isDecoratedClassDeclaration = isClassDeclaration(getOriginalNode(node));
+
+ const staticProperties = getInitializedProperties(node, /*isStatic*/ true);
+ const extendsClauseElement = getEffectiveBaseTypeNode(node);
+ const isDerivedClass = !!(extendsClauseElement && skipOuterExpressions(extendsClauseElement.expression).kind !== SyntaxKind.NullKeyword);
+
+ const classExpression = updateClassExpression(
+ node,
+ node.modifiers,
+ node.name,
+ node.typeParameters,
+ visitNodes(node.heritageClauses, visitor, isHeritageClause),
+ transformClassMembers(node, isDerivedClass)
+ );
+
+ if (some(staticProperties) || some(pendingExpressions)) {
+ if (isDecoratedClassDeclaration) {
+ Debug.assertDefined(pendingStatements, "Decorated classes transformed by TypeScript are expected to be within a variable declaration.");
+
+ // Write any pending expressions from elided or moved computed property names
+ if (pendingStatements && pendingExpressions && some(pendingExpressions)) {
+ pendingStatements.push(createExpressionStatement(inlineExpressions(pendingExpressions)));
+ }
+ pendingExpressions = savedPendingExpressions;
+
+ if (pendingStatements && some(staticProperties)) {
+ addInitializedPropertyStatements(pendingStatements, staticProperties, getInternalName(node));
+ }
+ return classExpression;
+ }
+ else {
+ const expressions: Expression[] = [];
+ const isClassWithConstructorReference = resolver.getNodeCheckFlags(node) & NodeCheckFlags.ClassWithConstructorReference;
+ const temp = createTempVariable(hoistVariableDeclaration, !!isClassWithConstructorReference);
+ if (isClassWithConstructorReference) {
+ // record an alias as the class name is not in scope for statics.
+ enableSubstitutionForClassAliases();
+ const alias = getSynthesizedClone(temp);
+ alias.autoGenerateFlags &= ~GeneratedIdentifierFlags.ReservedInNestedScopes;
+ classAliases[getOriginalNodeId(node)] = alias;
+ }
+
+ // To preserve the behavior of the old emitter, we explicitly indent
+ // the body of a class with static initializers.
+ setEmitFlags(classExpression, EmitFlags.Indented | getEmitFlags(classExpression));
+ expressions.push(startOnNewLine(createAssignment(temp, classExpression)));
+ // Add any pending expressions leftover from elided or relocated computed property names
+ addRange(expressions, map(pendingExpressions, startOnNewLine));
+ addRange(expressions, generateInitializedPropertyExpressions(staticProperties, temp));
+ expressions.push(startOnNewLine(temp));
+
+ pendingExpressions = savedPendingExpressions;
+ return inlineExpressions(expressions);
+ }
+ }
+
+ pendingExpressions = savedPendingExpressions;
+ return classExpression;
+ }
+
+ function transformClassMembers(node: ClassDeclaration | ClassExpression, isDerivedClass: boolean) {
+ const members: ClassElement[] = [];
+ const constructor = transformConstructor(node, isDerivedClass);
+ if (constructor) {
+ members.push(constructor);
+ }
+ addRange(members, visitNodes(node.members, classElementVisitor, isClassElement));
+ return setTextRange(createNodeArray(members), /*location*/ node.members);
+ }
+
+ function transformConstructor(node: ClassDeclaration | ClassExpression, isDerivedClass: boolean) {
+ const constructor = visitNode(getFirstConstructorWithBody(node), visitor, isConstructorDeclaration);
+ const containsPropertyInitializer = forEach(node.members, isInitializedProperty);
+ if (!containsPropertyInitializer) {
+ return constructor;
+ }
+ const parameters = visitParameterList(constructor ? constructor.parameters : undefined, visitor, context);
+ const body = transformConstructorBody(node, constructor, isDerivedClass);
+ if (!body) {
+ return undefined;
+ }
+ return startOnNewLine(
+ setOriginalNode(
+ setTextRange(
+ createConstructor(
+ /*decorators*/ undefined,
+ /*modifiers*/ undefined,
+ parameters,
+ body
+ ),
+ constructor || node
+ ),
+ constructor
+ )
+ );
+ }
+
+ function transformConstructorBody(node: ClassDeclaration | ClassExpression, constructor: ConstructorDeclaration | undefined, isDerivedClass: boolean) {
+ const properties = getInitializedProperties(node, /*isStatic*/ false);
+
+ // Only generate synthetic constructor when there are property initializers to move.
+ if (!constructor && !some(properties)) {
+ return visitFunctionBody(/*node*/ undefined, visitor, context);
+ }
+
+ resumeLexicalEnvironment();
+
+ let indexOfFirstStatement = 0;
+ let statements: Statement[] = [];
+
+ if (!constructor && isDerivedClass) {
+ // Add a synthetic `super` call:
+ //
+ // super(...arguments);
+ //
+ statements.push(
+ createExpressionStatement(
+ createCall(
+ createSuper(),
+ /*typeArguments*/ undefined,
+ [createSpread(createIdentifier("arguments"))]
+ )
+ )
+ );
+ }
+
+ if (constructor) {
+ indexOfFirstStatement = addPrologueDirectivesAndInitialSuperCall(constructor, statements, visitor);
+ }
+
+ // Add the property initializers. Transforms this:
+ //
+ // public x = 1;
+ //
+ // Into this:
+ //
+ // constructor() {
+ // this.x = 1;
+ // }
+ //
+ addInitializedPropertyStatements(statements, properties, createThis());
+
+ // Add existing statements, skipping the initial super call.
+ if (constructor) {
+ addRange(statements, visitNodes(constructor.body!.statements, visitor, isStatement, indexOfFirstStatement));
+ }
+
+ statements = mergeLexicalEnvironment(statements, endLexicalEnvironment());
+
+ return setTextRange(
+ createBlock(
+ setTextRange(
+ createNodeArray(statements),
+ /*location*/ constructor ? constructor.body!.statements : node.members
+ ),
+ /*multiLine*/ true
+ ),
+ /*location*/ constructor ? constructor.body : undefined
+ );
+ }
+
+ /**
+ * Generates assignment statements for property initializers.
+ *
+ * @param properties An array of property declarations to transform.
+ * @param receiver The receiver on which each property should be assigned.
+ */
+ function addInitializedPropertyStatements(statements: Statement[], properties: ReadonlyArray, receiver: LeftHandSideExpression) {
+ for (const property of properties) {
+ const statement = createExpressionStatement(transformInitializedProperty(property, receiver));
+ setSourceMapRange(statement, moveRangePastModifiers(property));
+ setCommentRange(statement, property);
+ setOriginalNode(statement, property);
+ statements.push(statement);
+ }
+ }
+
+ /**
+ * Generates assignment expressions for property initializers.
+ *
+ * @param properties An array of property declarations to transform.
+ * @param receiver The receiver on which each property should be assigned.
+ */
+ function generateInitializedPropertyExpressions(properties: ReadonlyArray, receiver: LeftHandSideExpression) {
+ const expressions: Expression[] = [];
+ for (const property of properties) {
+ const expression = transformInitializedProperty(property, receiver);
+ startOnNewLine(expression);
+ setSourceMapRange(expression, moveRangePastModifiers(property));
+ setCommentRange(expression, property);
+ setOriginalNode(expression, property);
+ expressions.push(expression);
+ }
+
+ return expressions;
+ }
+
+ /**
+ * Transforms a property initializer into an assignment statement.
+ *
+ * @param property The property declaration.
+ * @param receiver The object receiving the property assignment.
+ */
+ function transformInitializedProperty(property: PropertyDeclaration, receiver: LeftHandSideExpression) {
+ // We generate a name here in order to reuse the value cached by the relocated computed name expression (which uses the same generated name)
+ const propertyName = isComputedPropertyName(property.name) && !isSimpleInlineableExpression(property.name.expression)
+ ? updateComputedPropertyName(property.name, getGeneratedNameForNode(property.name))
+ : property.name;
+ const initializer = visitNode(property.initializer!, visitor, isExpression);
+ const memberAccess = createMemberAccessForPropertyName(receiver, propertyName, /*location*/ propertyName);
+
+ return createAssignment(memberAccess, initializer);
+ }
+
+ function enableSubstitutionForClassAliases() {
+ if ((enabledSubstitutions & ClassPropertySubstitutionFlags.ClassAliases) === 0) {
+ enabledSubstitutions |= ClassPropertySubstitutionFlags.ClassAliases;
+
+ // We need to enable substitutions for identifiers. This allows us to
+ // substitute class names inside of a class declaration.
+ context.enableSubstitution(SyntaxKind.Identifier);
+
+ // Keep track of class aliases.
+ classAliases = [];
+ }
+ }
+
+ /**
+ * Hooks node substitutions.
+ *
+ * @param hint The context for the emitter.
+ * @param node The node to substitute.
+ */
+ function onSubstituteNode(hint: EmitHint, node: Node) {
+ node = previousOnSubstituteNode(hint, node);
+ if (hint === EmitHint.Expression) {
+ return substituteExpression(node as Expression);
+ }
+ return node;
+ }
+
+ function substituteExpression(node: Expression) {
+ switch (node.kind) {
+ case SyntaxKind.Identifier:
+ return substituteExpressionIdentifier(node as Identifier);
+ }
+ return node;
+ }
+
+ function substituteExpressionIdentifier(node: Identifier): Expression {
+ return trySubstituteClassAlias(node) || node;
+ }
+
+ function trySubstituteClassAlias(node: Identifier): Expression | undefined {
+ if (enabledSubstitutions & ClassPropertySubstitutionFlags.ClassAliases) {
+ if (resolver.getNodeCheckFlags(node) & NodeCheckFlags.ConstructorReferenceInClass) {
+ // Due to the emit for class decorators, any reference to the class from inside of the class body
+ // must instead be rewritten to point to a temporary variable to avoid issues with the double-bind
+ // behavior of class names in ES6.
+ // Also, when emitting statics for class expressions, we must substitute a class alias for
+ // constructor references in static property initializers.
+ const declaration = resolver.getReferencedValueDeclaration(node);
+ if (declaration) {
+ const classAlias = classAliases[declaration.id!]; // TODO: GH#18217
+ if (classAlias) {
+ const clone = getSynthesizedClone(classAlias);
+ setSourceMapRange(clone, node);
+ setCommentRange(clone, node);
+ return clone;
+ }
+ }
+ }
+ }
+
+ return undefined;
+ }
+
+
+ /**
+ * If the name is a computed property, this function transforms it, then either returns an expression which caches the
+ * value of the result or the expression itself if the value is either unused or safe to inline into multiple locations
+ * @param shouldHoist Does the expression need to be reused? (ie, for an initializer or a decorator)
+ */
+ function getPropertyNameExpressionIfNeeded(name: PropertyName, shouldHoist: boolean): Expression | undefined {
+ if (isComputedPropertyName(name)) {
+ const expression = visitNode(name.expression, visitor, isExpression);
+ const innerExpression = skipPartiallyEmittedExpressions(expression);
+ const inlinable = isSimpleInlineableExpression(innerExpression);
+ const alreadyTransformed = isAssignmentExpression(innerExpression) && isGeneratedIdentifier(innerExpression.left);
+ if (!alreadyTransformed && !inlinable && shouldHoist) {
+ const generatedName = getGeneratedNameForNode(name);
+ hoistVariableDeclaration(generatedName);
+ return createAssignment(generatedName, expression);
+ }
+ return (inlinable || isIdentifier(innerExpression)) ? undefined : expression;
+ }
+ }
+ }
+
+}
diff --git a/src/compiler/transformers/ts.ts b/src/compiler/transformers/ts.ts
index a70fb820e79..adfe16c73f6 100644
--- a/src/compiler/transformers/ts.ts
+++ b/src/compiler/transformers/ts.ts
@@ -64,6 +64,7 @@ namespace ts {
let currentLexicalScope: SourceFile | Block | ModuleBlock | CaseBlock;
let currentNameScope: ClassDeclaration | undefined;
let currentScopeFirstDeclarationsOfName: UnderscoreEscapedMap | undefined;
+ let currentClassHasParameterProperties: boolean | undefined;
/**
* Keeps track of whether expression substitution has been enabled for specific edge cases.
@@ -83,12 +84,6 @@ namespace ts {
*/
let applicableSubstitutions: TypeScriptSubstitutionFlags;
- /**
- * Tracks what computed name expressions originating from elided names must be inlined
- * at the next execution site, in document order
- */
- let pendingExpressions: Expression[] | undefined;
-
return transformSourceFileOrBundle;
function transformSourceFileOrBundle(node: SourceFile | Bundle) {
@@ -136,6 +131,7 @@ namespace ts {
const savedCurrentScope = currentLexicalScope;
const savedCurrentNameScope = currentNameScope;
const savedCurrentScopeFirstDeclarationsOfName = currentScopeFirstDeclarationsOfName;
+ const savedCurrentClassHasParameterProperties = currentClassHasParameterProperties;
// Handle state changes before visiting a node.
onBeforeVisitNode(node);
@@ -149,6 +145,7 @@ namespace ts {
currentLexicalScope = savedCurrentScope;
currentNameScope = savedCurrentNameScope;
+ currentClassHasParameterProperties = savedCurrentClassHasParameterProperties;
return visited;
}
@@ -321,6 +318,9 @@ namespace ts {
return undefined;
case SyntaxKind.PropertyDeclaration:
+ // Property declarations are not TypeScript syntax, but they must be visited
+ // for the decorator transformation.
+ return visitPropertyDeclaration(node as PropertyDeclaration);
case SyntaxKind.IndexSignature:
case SyntaxKind.GetAccessor:
case SyntaxKind.SetAccessor:
@@ -437,7 +437,6 @@ namespace ts {
// - decorators
// - optional `implements` heritage clause
// - parameter property assignments in the constructor
- // - property declarations
// - index signatures
// - method overload signatures
return visitClassDeclaration(node);
@@ -449,7 +448,6 @@ namespace ts {
// - decorators
// - optional `implements` heritage clause
// - parameter property assignments in the constructor
- // - property declarations
// - index signatures
// - method overload signatures
return visitClassExpression(node);
@@ -611,10 +609,6 @@ namespace ts {
if (!isClassLikeDeclarationWithTypeScriptSyntax(node) && !(currentNamespace && hasModifier(node, ModifierFlags.Export))) {
return visitEachChild(node, visitor, context);
}
-
- const savedPendingExpressions = pendingExpressions;
- pendingExpressions = undefined;
-
const staticProperties = getInitializedProperties(node, /*isStatic*/ true);
const facts = getClassFacts(node, staticProperties);
@@ -624,25 +618,11 @@ namespace ts {
const name = node.name || (facts & ClassFacts.NeedsName ? getGeneratedNameForNode(node) : undefined);
const classStatement = facts & ClassFacts.HasConstructorDecorators
- ? createClassDeclarationHeadWithDecorators(node, name, facts)
+ ? createClassDeclarationHeadWithDecorators(node, name)
: createClassDeclarationHeadWithoutDecorators(node, name, facts);
let statements: Statement[] = [classStatement];
- // Write any pending expressions from elided or moved computed property names
- if (some(pendingExpressions)) {
- statements.push(createExpressionStatement(inlineExpressions(pendingExpressions!)));
- }
- pendingExpressions = savedPendingExpressions;
-
- // Emit static property assignment. Because classDeclaration is lexically evaluated,
- // it is safe to emit static property assignment after classDeclaration
- // From ES6 specification:
- // HasLexicalDeclaration (N) : Determines if the argument identifier has a binding in this environment record that was created using
- // a lexical declaration such as a LexicalDeclaration or a ClassDeclaration.
- if (facts & ClassFacts.HasStaticInitializedProperties) {
- addInitializedPropertyStatements(statements, staticProperties, facts & ClassFacts.UseImmediatelyInvokedFunctionExpression ? getInternalName(node) : getLocalName(node));
- }
// Write any decorators of the node.
addClassElementDecorationStatements(statements, node, /*isStatic*/ false);
@@ -745,7 +725,7 @@ namespace ts {
name,
/*typeParameters*/ undefined,
visitNodes(node.heritageClauses, visitor, isHeritageClause),
- transformClassMembers(node, (facts & ClassFacts.IsDerivedClass) !== 0)
+ transformClassMembers(node)
);
// To better align with the old emitter, we should not emit a trailing source map
@@ -765,7 +745,7 @@ namespace ts {
* Transforms a decorated class declaration and appends the resulting statements. If
* the class requires an alias to avoid issues with double-binding, the alias is returned.
*/
- function createClassDeclarationHeadWithDecorators(node: ClassDeclaration, name: Identifier | undefined, facts: ClassFacts) {
+ function createClassDeclarationHeadWithDecorators(node: ClassDeclaration, name: Identifier | undefined) {
// When we emit an ES6 class that has a class decorator, we must tailor the
// emit to certain specific cases.
//
@@ -860,7 +840,7 @@ namespace ts {
// ${members}
// }
const heritageClauses = visitNodes(node.heritageClauses, visitor, isHeritageClause);
- const members = transformClassMembers(node, (facts & ClassFacts.IsDerivedClass) !== 0);
+ const members = transformClassMembers(node);
const classExpression = createClassExpression(/*modifiers*/ undefined, name, /*typeParameters*/ undefined, heritageClauses, members);
setOriginalNode(classExpression, node);
setTextRange(classExpression, location);
@@ -888,49 +868,19 @@ namespace ts {
return visitEachChild(node, visitor, context);
}
- const savedPendingExpressions = pendingExpressions;
- pendingExpressions = undefined;
-
- const staticProperties = getInitializedProperties(node, /*isStatic*/ true);
- const heritageClauses = visitNodes(node.heritageClauses, visitor, isHeritageClause);
- const members = transformClassMembers(node, some(heritageClauses, c => c.token === SyntaxKind.ExtendsKeyword));
+ const members = transformClassMembers(node);
const classExpression = createClassExpression(
/*modifiers*/ undefined,
node.name,
/*typeParameters*/ undefined,
- heritageClauses,
+ node.heritageClauses,
members
);
setOriginalNode(classExpression, node);
setTextRange(classExpression, node);
- if (some(staticProperties) || some(pendingExpressions)) {
- const expressions: Expression[] = [];
- const isClassWithConstructorReference = resolver.getNodeCheckFlags(node) & NodeCheckFlags.ClassWithConstructorReference;
- const temp = createTempVariable(hoistVariableDeclaration, !!isClassWithConstructorReference);
- if (isClassWithConstructorReference) {
- // record an alias as the class name is not in scope for statics.
- enableSubstitutionForClassAliases();
- const alias = getSynthesizedClone(temp);
- alias.autoGenerateFlags &= ~GeneratedIdentifierFlags.ReservedInNestedScopes;
- classAliases[getOriginalNodeId(node)] = alias;
- }
-
- // To preserve the behavior of the old emitter, we explicitly indent
- // the body of a class with static initializers.
- setEmitFlags(classExpression, EmitFlags.Indented | getEmitFlags(classExpression));
- expressions.push(startOnNewLine(createAssignment(temp, classExpression)));
- // Add any pending expressions leftover from elided or relocated computed property names
- addRange(expressions, map(pendingExpressions, startOnNewLine));
- pendingExpressions = savedPendingExpressions;
- addRange(expressions, generateInitializedPropertyExpressions(staticProperties, temp));
- expressions.push(startOnNewLine(temp));
- return inlineExpressions(expressions);
- }
-
- pendingExpressions = savedPendingExpressions;
return classExpression;
}
@@ -938,61 +888,81 @@ namespace ts {
* Transforms the members of a class.
*
* @param node The current class.
- * @param isDerivedClass A value indicating whether the class has an extends clause that does not extend 'null'.
*/
- function transformClassMembers(node: ClassDeclaration | ClassExpression, isDerivedClass: boolean) {
+ function transformClassMembers(node: ClassDeclaration | ClassExpression) {
const members: ClassElement[] = [];
- const constructor = transformConstructor(node, isDerivedClass);
- if (constructor) {
- members.push(constructor);
- }
-
- addRange(members, visitNodes(node.members, classElementVisitor, isClassElement));
- return setTextRange(createNodeArray(members), /*location*/ node.members);
- }
-
- /**
- * Transforms (or creates) a constructor for a class.
- *
- * @param node The current class.
- * @param isDerivedClass A value indicating whether the class has an extends clause that does not extend 'null'.
- */
- function transformConstructor(node: ClassDeclaration | ClassExpression, isDerivedClass: boolean) {
- // Check if we have property assignment inside class declaration.
- // If there is a property assignment, we need to emit constructor whether users define it or not
- // If there is no property assignment, we can omit constructor if users do not define it
+ const existingMembers = visitNodes(node.members, classElementVisitor, isClassElement);
const constructor = getFirstConstructorWithBody(node);
- const hasInstancePropertyWithInitializer = forEach(node.members, isInstanceInitializedProperty);
- const hasParameterPropertyAssignments = constructor &&
- constructor.transformFlags & TransformFlags.ContainsTypeScriptClassSyntax &&
- forEach(constructor.parameters, isParameterWithPropertyAssignment);
+ const parametersWithPropertyAssignments =
+ constructor && hasTypeScriptClassSyntax(constructor)
+ ? filter(constructor.parameters, isParameterPropertyDeclaration)
+ : undefined;
+ if (some(parametersWithPropertyAssignments) && constructor) {
+ currentClassHasParameterProperties = true;
- // If the class does not contain nodes that require a synthesized constructor,
- // accept the current constructor if it exists.
- if (!hasInstancePropertyWithInitializer && !hasParameterPropertyAssignments) {
- return visitEachChild(constructor, visitor, context);
- }
-
- const parameters = transformConstructorParameters(constructor);
- const body = transformConstructorBody(node, constructor, isDerivedClass);
-
- // constructor(${parameters}) {
- // ${body}
- // }
- return startOnNewLine(
- setOriginalNode(
- setTextRange(
- createConstructor(
+ // Create property declarations for constructor parameter properties.
+ addRange(
+ members,
+ parametersWithPropertyAssignments.map(param =>
+ createProperty(
/*decorators*/ undefined,
/*modifiers*/ undefined,
- parameters,
- body
+ param.name,
+ /*questionOrExclamationToken*/ undefined,
+ /*type*/ undefined,
+ /*initializer*/ undefined
+ )
+ )
+ );
+
+ const parameters = transformConstructorParameters(constructor);
+ const body = transformConstructorBody(node.members, constructor, parametersWithPropertyAssignments);
+ members.push(startOnNewLine(
+ setOriginalNode(
+ setTextRange(
+ createConstructor(
+ /*decorators*/ undefined,
+ /*modifiers*/ undefined,
+ parameters,
+ body
+ ),
+ constructor
),
- constructor || node
- ),
- constructor
- )
- );
+ constructor
+ )
+ ));
+ addRange(
+ members,
+ visitNodes(
+ existingMembers,
+ member => {
+ if (isPropertyDeclaration(member) && !hasStaticModifier(member) && !!member.initializer) {
+ const updated = updateProperty(
+ member,
+ member.decorators,
+ member.modifiers,
+ member.name,
+ member.questionToken,
+ member.type,
+ /*initializer*/ undefined
+ );
+ setCommentRange(updated, node);
+ setSourceMapRange(updated, node);
+ return updated;
+ }
+ return member;
+ },
+ isClassElement
+ )
+ );
+ }
+ else {
+ if (constructor) {
+ members.push(visitEachChild(constructor, visitor, context));
+ }
+ addRange(members, existingMembers);
+ }
+ return setTextRange(createNodeArray(members), /*location*/ node.members);
}
/**
@@ -1001,7 +971,7 @@ namespace ts {
*
* @param constructor The constructor declaration.
*/
- function transformConstructorParameters(constructor: ConstructorDeclaration | undefined) {
+ function transformConstructorParameters(constructor: ConstructorDeclaration) {
// The ES2015 spec specifies in 14.5.14. Runtime Semantics: ClassDefinitionEvaluation:
// If constructor is empty, then
// If ClassHeritag_eopt is present and protoParent is not null, then
@@ -1022,70 +992,54 @@ namespace ts {
}
/**
- * Transforms (or creates) a constructor body for a class with parameter property
- * assignments or instance property initializers.
+ * Transforms (or creates) a constructor body for a class with parameter property assignments.
*
* @param node The current class.
* @param constructor The current class constructor.
- * @param isDerivedClass A value indicating whether the class has an extends clause that does not extend 'null'.
*/
- function transformConstructorBody(node: ClassExpression | ClassDeclaration, constructor: ConstructorDeclaration | undefined, isDerivedClass: boolean) {
+ function transformConstructorBody(members: NodeArray, constructor: ConstructorDeclaration, propertyAssignments: ReadonlyArray) {
let statements: Statement[] = [];
let indexOfFirstStatement = 0;
resumeLexicalEnvironment();
- if (constructor) {
- indexOfFirstStatement = addPrologueDirectivesAndInitialSuperCall(constructor, statements);
+ indexOfFirstStatement = addPrologueDirectivesAndInitialSuperCall(constructor, statements, visitor);
- // Add parameters with property assignments. Transforms this:
- //
- // constructor (public x, public y) {
- // }
- //
- // Into this:
- //
- // constructor (x, y) {
- // this.x = x;
- // this.y = y;
- // }
- //
- const propertyAssignments = getParametersWithPropertyAssignments(constructor);
- addRange(statements, map(propertyAssignments, transformParameterWithPropertyAssignment));
- }
- else if (isDerivedClass) {
- // Add a synthetic `super` call:
- //
- // super(...arguments);
- //
- statements.push(
- createExpressionStatement(
- createCall(
- createSuper(),
- /*typeArguments*/ undefined,
- [createSpread(createIdentifier("arguments"))]
- )
- )
- );
- }
-
- // Add the property initializers. Transforms this:
+ // Add parameters with property assignments. Transforms this:
//
- // public x = 1;
+ // constructor (public x, public y) {
+ // }
//
// Into this:
//
- // constructor() {
- // this.x = 1;
+ // constructor (x, y) {
+ // this.x = x;
+ // this.y = y;
// }
//
- const properties = getInitializedProperties(node, /*isStatic*/ false);
- addInitializedPropertyStatements(statements, properties, createThis());
+ addRange(statements, map(propertyAssignments, transformParameterWithPropertyAssignment));
- if (constructor) {
- // The class already had a constructor, so we should add the existing statements, skipping the initial super call.
- addRange(statements, visitNodes(constructor.body!.statements, visitor, isStatement, indexOfFirstStatement));
- }
+ // Get property initializers.
+ const classBodyProperties = members.filter(member => isPropertyDeclaration(member) && !hasStaticModifier(member) && !!member.initializer) as PropertyDeclaration[];
+ addRange(statements, classBodyProperties.map(
+ prop => {
+ const name = prop.name;
+ const lhs = (!isComputedPropertyName(name) || isSimpleInlineableExpression(name.expression)) ?
+ createMemberAccessForPropertyName(createThis(), name, prop) :
+ createElementAccess(createThis(), getGeneratedNameForNode(name));
+ const initializerNode = createExpressionStatement(
+ createAssignment(lhs, prop.initializer!)
+ );
+ setOriginalNode(initializerNode, prop);
+ setTextRange(initializerNode, prop);
+ setCommentRange(initializerNode, prop);
+ setSourceMapRange(initializerNode, prop);
+ return initializerNode;
+ }
+ ));
+
+ // Add the existing statements, skipping the initial super call.
+ addRange(statements, visitNodes(constructor.body!.statements, visitor, isStatement, indexOfFirstStatement));
// End the lexical environment.
statements = mergeLexicalEnvironment(statements, endLexicalEnvironment());
@@ -1093,7 +1047,7 @@ namespace ts {
createBlock(
setTextRange(
createNodeArray(statements),
- /*location*/ constructor ? constructor.body!.statements : node.members
+ /*location*/ constructor ? constructor.body!.statements : members
),
/*multiLine*/ true
),
@@ -1101,61 +1055,16 @@ namespace ts {
);
}
- /**
- * Adds super call and preceding prologue directives into the list of statements.
- *
- * @param ctor The constructor node.
- * @returns index of the statement that follows super call
- */
- function addPrologueDirectivesAndInitialSuperCall(ctor: ConstructorDeclaration, result: Statement[]): number {
- if (ctor.body) {
- const statements = ctor.body.statements;
- // add prologue directives to the list (if any)
- const index = addPrologue(result, statements, /*ensureUseStrict*/ false, visitor);
- if (index === statements.length) {
- // list contains nothing but prologue directives (or empty) - exit
- return index;
- }
-
- const statement = statements[index];
- if (statement.kind === SyntaxKind.ExpressionStatement && isSuperCall((statement).expression)) {
- result.push(visitNode(statement, visitor, isStatement));
- return index + 1;
- }
-
- return index;
- }
-
- return 0;
- }
-
- /**
- * Gets all parameters of a constructor that should be transformed into property assignments.
- *
- * @param node The constructor node.
- */
- function getParametersWithPropertyAssignments(node: ConstructorDeclaration): ReadonlyArray {
- return filter(node.parameters, isParameterWithPropertyAssignment);
- }
-
- /**
- * Determines whether a parameter should be transformed into a property assignment.
- *
- * @param parameter The parameter node.
- */
- function isParameterWithPropertyAssignment(parameter: ParameterDeclaration) {
- return hasModifier(parameter, ModifierFlags.ParameterPropertyModifier)
- && isIdentifier(parameter.name);
- }
-
/**
* Transforms a parameter into a property assignment statement.
*
* @param node The parameter declaration.
*/
- function transformParameterWithPropertyAssignment(node: ParameterDeclaration) {
- Debug.assert(isIdentifier(node.name));
- const name = node.name as Identifier;
+ function transformParameterWithPropertyAssignment(node: ParameterPropertyDeclaration) {
+ const name = node.name;
+ if (!isIdentifier(name)) {
+ return undefined;
+ }
const propertyName = getMutableClone(name);
setEmitFlags(propertyName, EmitFlags.NoComments | EmitFlags.NoSourceMap);
@@ -1184,99 +1093,6 @@ namespace ts {
);
}
- /**
- * Gets all property declarations with initializers on either the static or instance side of a class.
- *
- * @param node The class node.
- * @param isStatic A value indicating whether to get properties from the static or instance side of the class.
- */
- function getInitializedProperties(node: ClassExpression | ClassDeclaration, isStatic: boolean): ReadonlyArray {
- return filter(node.members, isStatic ? isStaticInitializedProperty : isInstanceInitializedProperty);
- }
-
- /**
- * Gets a value indicating whether a class element is a static property declaration with an initializer.
- *
- * @param member The class element node.
- */
- function isStaticInitializedProperty(member: ClassElement): member is PropertyDeclaration {
- return isInitializedProperty(member, /*isStatic*/ true);
- }
-
- /**
- * Gets a value indicating whether a class element is an instance property declaration with an initializer.
- *
- * @param member The class element node.
- */
- function isInstanceInitializedProperty(member: ClassElement): member is PropertyDeclaration {
- return isInitializedProperty(member, /*isStatic*/ false);
- }
-
- /**
- * Gets a value indicating whether a class element is either a static or an instance property declaration with an initializer.
- *
- * @param member The class element node.
- * @param isStatic A value indicating whether the member should be a static or instance member.
- */
- function isInitializedProperty(member: ClassElement, isStatic: boolean) {
- return member.kind === SyntaxKind.PropertyDeclaration
- && isStatic === hasModifier(member, ModifierFlags.Static)
- && (member).initializer !== undefined;
- }
-
- /**
- * Generates assignment statements for property initializers.
- *
- * @param properties An array of property declarations to transform.
- * @param receiver The receiver on which each property should be assigned.
- */
- function addInitializedPropertyStatements(statements: Statement[], properties: ReadonlyArray, receiver: LeftHandSideExpression) {
- for (const property of properties) {
- const statement = createExpressionStatement(transformInitializedProperty(property, receiver));
- setSourceMapRange(statement, moveRangePastModifiers(property));
- setCommentRange(statement, property);
- setOriginalNode(statement, property);
- statements.push(statement);
- }
- }
-
- /**
- * Generates assignment expressions for property initializers.
- *
- * @param properties An array of property declarations to transform.
- * @param receiver The receiver on which each property should be assigned.
- */
- function generateInitializedPropertyExpressions(properties: ReadonlyArray, receiver: LeftHandSideExpression) {
- const expressions: Expression[] = [];
- for (const property of properties) {
- const expression = transformInitializedProperty(property, receiver);
- startOnNewLine(expression);
- setSourceMapRange(expression, moveRangePastModifiers(property));
- setCommentRange(expression, property);
- setOriginalNode(expression, property);
- expressions.push(expression);
- }
-
- return expressions;
- }
-
- /**
- * Transforms a property initializer into an assignment statement.
- *
- * @param property The property declaration.
- * @param receiver The object receiving the property assignment.
- */
- function transformInitializedProperty(property: PropertyDeclaration, receiver: LeftHandSideExpression) {
- // We generate a name here in order to reuse the value cached by the relocated computed name expression (which uses the same generated name)
- const propertyName = isComputedPropertyName(property.name) && !isSimpleInlineableExpression(property.name.expression)
- ? updateComputedPropertyName(property.name, getGeneratedNameForNode(property.name))
- : property.name;
- const initializer = visitNode(property.initializer!, visitor, isExpression);
- const memberAccess = createMemberAccessForPropertyName(receiver, propertyName, /*location*/ propertyName);
-
- return createAssignment(memberAccess, initializer);
- }
-
/**
* Gets either the static or instance members of a class that are decorated, or have
* parameters that are decorated.
@@ -2144,16 +1960,6 @@ namespace ts {
: createIdentifier("BigInt");
}
- /**
- * A simple inlinable expression is an expression which can be copied into multiple locations
- * without risk of repeating any sideeffects and whose value could not possibly change between
- * any such locations
- */
- function isSimpleInlineableExpression(expression: Expression) {
- return !isIdentifier(expression) && isSimpleCopiableExpression(expression) ||
- isWellKnownSymbolSyntactically(expression);
- }
-
/**
* Gets an expression that represents a property name. For a computed property, a
* name is generated for the node.
@@ -2175,26 +1981,6 @@ namespace ts {
}
}
- /**
- * If the name is a computed property, this function transforms it, then either returns an expression which caches the
- * value of the result or the expression itself if the value is either unused or safe to inline into multiple locations
- * @param shouldHoist Does the expression need to be reused? (ie, for an initializer or a decorator)
- * @param omitSimple Should expressions with no observable side-effects be elided? (ie, the expression is not hoisted for a decorator or initializer and is a literal)
- */
- function getPropertyNameExpressionIfNeeded(name: PropertyName, shouldHoist: boolean, omitSimple: boolean): Expression | undefined {
- if (isComputedPropertyName(name)) {
- const expression = visitNode(name.expression, visitor, isExpression);
- const innerExpression = skipPartiallyEmittedExpressions(expression);
- const inlinable = isSimpleInlineableExpression(innerExpression);
- if (!inlinable && shouldHoist) {
- const generatedName = getGeneratedNameForNode(name);
- hoistVariableDeclaration(generatedName);
- return createAssignment(generatedName, expression);
- }
- return (omitSimple && (inlinable || isIdentifier(innerExpression))) ? undefined : expression;
- }
- }
-
/**
* Visits the property name of a class element, for use when emitting property
* initializers. For a computed property on a node with decorators, a temporary
@@ -2204,18 +1990,20 @@ namespace ts {
*/
function visitPropertyNameOfClassElement(member: ClassElement): PropertyName {
const name = member.name!;
- let expr = getPropertyNameExpressionIfNeeded(name, some(member.decorators), /*omitSimple*/ false);
- if (expr) { // expr only exists if `name` is a computed property name
- // Inline any pending expressions from previous elided or relocated computed property name expressions in order to preserve execution order
- if (some(pendingExpressions)) {
- expr = inlineExpressions([...pendingExpressions, expr]);
- pendingExpressions.length = 0;
+ // Computed property names need to be transformed into a hoisted variable when they are used more than once.
+ // The names are used more than once when:
+ // - the property is non-static and its initializer is moved to the constructor (when there are parameter property assignments).
+ // - the property has a decorator.
+ if (isComputedPropertyName(name) && ((!hasStaticModifier(member) && currentClassHasParameterProperties) || some(member.decorators))) {
+ const expression = visitNode(name.expression, visitor, isExpression);
+ const innerExpression = skipPartiallyEmittedExpressions(expression);
+ if (!isSimpleInlineableExpression(innerExpression)) {
+ const generatedName = getGeneratedNameForNode(name);
+ hoistVariableDeclaration(generatedName);
+ return updateComputedPropertyName(name, createAssignment(generatedName, expression));
}
- return updateComputedPropertyName(name as ComputedPropertyName, expr);
- }
- else {
- return name;
}
+ return visitNode(name, visitor, isPropertyName);
}
/**
@@ -2261,12 +2049,23 @@ namespace ts {
return !nodeIsMissing(node.body);
}
- function visitPropertyDeclaration(node: PropertyDeclaration): undefined {
- const expr = getPropertyNameExpressionIfNeeded(node.name, some(node.decorators) || !!node.initializer, /*omitSimple*/ true);
- if (expr && !isSimpleInlineableExpression(expr)) {
- (pendingExpressions || (pendingExpressions = [])).push(expr);
+ function visitPropertyDeclaration(node: PropertyDeclaration) {
+ const updated = updateProperty(
+ node,
+ /*decorators*/ undefined,
+ visitNodes(node.modifiers, visitor, isModifier),
+ visitPropertyNameOfClassElement(node),
+ /*questionOrExclamationToken*/ undefined,
+ /*type*/ undefined,
+ visitNode(node.initializer, visitor)
+ );
+ if (updated !== node) {
+ // While we emit the source map for the node after skipping decorators and modifiers,
+ // we need to emit the comments for the original range.
+ setCommentRange(updated, node);
+ setSourceMapRange(updated, moveRangePastDecorators(node));
}
- return undefined;
+ return updated;
}
function visitConstructor(node: ConstructorDeclaration) {
diff --git a/src/compiler/transformers/utilities.ts b/src/compiler/transformers/utilities.ts
index 09239d7765f..f5f8a298a49 100644
--- a/src/compiler/transformers/utilities.ts
+++ b/src/compiler/transformers/utilities.ts
@@ -240,6 +240,47 @@ namespace ts {
isIdentifier(expression);
}
+ /**
+ * A simple inlinable expression is an expression which can be copied into multiple locations
+ * without risk of repeating any sideeffects and whose value could not possibly change between
+ * any such locations
+ */
+ export function isSimpleInlineableExpression(expression: Expression) {
+ return !isIdentifier(expression) && isSimpleCopiableExpression(expression) ||
+ isWellKnownSymbolSyntactically(expression);
+ }
+
+ /**
+ * Adds super call and preceding prologue directives into the list of statements.
+ *
+ * @param ctor The constructor node.
+ * @param result The list of statements.
+ * @param visitor The visitor to apply to each node added to the result array.
+ * @returns index of the statement that follows super call
+ */
+ export function addPrologueDirectivesAndInitialSuperCall(ctor: ConstructorDeclaration, result: Statement[], visitor: Visitor): number {
+ if (ctor.body) {
+ const statements = ctor.body.statements;
+ // add prologue directives to the list (if any)
+ const index = addPrologue(result, statements, /*ensureUseStrict*/ false, visitor);
+ if (index === statements.length) {
+ // list contains nothing but prologue directives (or empty) - exit
+ return index;
+ }
+
+ const statement = statements[index];
+ if (statement.kind === SyntaxKind.ExpressionStatement && isSuperCall((statement).expression)) {
+ result.push(visitNode(statement, visitor, isStatement));
+ return index + 1;
+ }
+
+ return index;
+ }
+
+ return 0;
+ }
+
+
/**
* @param input Template string input strings
* @param args Names which need to be made file-level unique
@@ -255,4 +296,43 @@ namespace ts {
return result;
};
}
+
+ /**
+ * Gets all property declarations with initializers on either the static or instance side of a class.
+ *
+ * @param node The class node.
+ * @param isStatic A value indicating whether to get properties from the static or instance side of the class.
+ */
+ export function getInitializedProperties(node: ClassExpression | ClassDeclaration, isStatic: boolean): ReadonlyArray {
+ return filter(node.members, isStatic ? isStaticInitializedProperty : isInstanceInitializedProperty);
+ }
+
+ /**
+ * Gets a value indicating whether a class element is a static property declaration with an initializer.
+ *
+ * @param member The class element node.
+ */
+ export function isStaticInitializedProperty(member: ClassElement): member is PropertyDeclaration & { initializer: Expression; } {
+ return isInitializedProperty(member) && hasStaticModifier(member);
+ }
+
+ /**
+ * Gets a value indicating whether a class element is an instance property declaration with an initializer.
+ *
+ * @param member The class element node.
+ */
+ export function isInstanceInitializedProperty(member: ClassElement): member is PropertyDeclaration & { initializer: Expression; } {
+ return isInitializedProperty(member) && !hasStaticModifier(member);
+ }
+
+ /**
+ * Gets a value indicating whether a class element is either a static or an instance property declaration with an initializer.
+ *
+ * @param member The class element node.
+ * @param isStatic A value indicating whether the member should be a static or instance member.
+ */
+ export function isInitializedProperty(member: ClassElement): member is PropertyDeclaration & { initializer: Expression; } {
+ return member.kind === SyntaxKind.PropertyDeclaration
+ && (member).initializer !== undefined;
+ }
}
\ No newline at end of file
diff --git a/src/compiler/tsbuild.ts b/src/compiler/tsbuild.ts
index a5eae8922da..94718a1b368 100644
--- a/src/compiler/tsbuild.ts
+++ b/src/compiler/tsbuild.ts
@@ -1,64 +1,5 @@
-// Currently we do not want to expose API for build, we should work out the API, and then expose it just like we did for builder/watch
/*@internal*/
namespace ts {
- const minimumDate = new Date(-8640000000000000);
- const maximumDate = new Date(8640000000000000);
-
- export interface BuildHost {
- verbose(diag: DiagnosticMessage, ...args: string[]): void;
- error(diag: DiagnosticMessage, ...args: string[]): void;
- errorDiagnostic(diag: Diagnostic): void;
- message(diag: DiagnosticMessage, ...args: string[]): void;
- }
-
- interface DependencyGraph {
- buildQueue: ResolvedConfigFileName[];
- /** value in config File map is true if project is referenced using prepend */
- referencingProjectsMap: ConfigFileMap>;
- }
-
- export interface BuildOptions extends OptionsBase {
- dry?: boolean;
- force?: boolean;
- verbose?: boolean;
-
- /*@internal*/ clean?: boolean;
- /*@internal*/ watch?: boolean;
- /*@internal*/ help?: boolean;
-
- preserveWatchOutput?: boolean;
- listEmittedFiles?: boolean;
- listFiles?: boolean;
- pretty?: boolean;
- incremental?: boolean;
-
- traceResolution?: boolean;
- /* @internal */ diagnostics?: boolean;
- /* @internal */ extendedDiagnostics?: boolean;
- }
-
- enum BuildResultFlags {
- None = 0,
-
- /**
- * No errors of any kind occurred during build
- */
- Success = 1 << 0,
- /**
- * None of the .d.ts files emitted by this build were
- * different from the existing files on disk
- */
- DeclarationOutputUnchanged = 1 << 1,
-
- ConfigFileErrors = 1 << 2,
- SyntaxErrors = 1 << 3,
- TypeErrors = 1 << 4,
- DeclarationEmitErrors = 1 << 5,
- EmitErrors = 1 << 6,
-
- AnyErrors = ConfigFileErrors | SyntaxErrors | TypeErrors | DeclarationEmitErrors | EmitErrors
- }
-
export enum UpToDateStatusType {
Unbuildable,
UpToDate,
@@ -200,80 +141,93 @@ namespace ts {
}
}
- interface FileMap {
- setValue(fileName: U, value: T): void;
- getValue(fileName: U): T | undefined;
- hasKey(fileName: U): boolean;
- removeKey(fileName: U): void;
- forEach(action: (value: T, key: V) => void): void;
- getSize(): number;
+ export function resolveConfigFileProjectName(project: string): ResolvedConfigFileName {
+ if (fileExtensionIs(project, Extension.Json)) {
+ return project as ResolvedConfigFileName;
+ }
+
+ return combinePaths(project, "tsconfig.json") as ResolvedConfigFileName;
+ }
+}
+
+namespace ts {
+ const minimumDate = new Date(-8640000000000000);
+ const maximumDate = new Date(8640000000000000);
+
+ export interface BuildOptions {
+ dry?: boolean;
+ force?: boolean;
+ verbose?: boolean;
+
+ /*@internal*/ clean?: boolean;
+ /*@internal*/ watch?: boolean;
+ /*@internal*/ help?: boolean;
+
+ /*@internal*/ preserveWatchOutput?: boolean;
+ /*@internal*/ listEmittedFiles?: boolean;
+ /*@internal*/ listFiles?: boolean;
+ /*@internal*/ pretty?: boolean;
+ incremental?: boolean;
+
+ traceResolution?: boolean;
+ /* @internal */ diagnostics?: boolean;
+ /* @internal */ extendedDiagnostics?: boolean;
+
+ [option: string]: CompilerOptionsValue | undefined;
+ }
+
+ enum BuildResultFlags {
+ None = 0,
+
+ /**
+ * No errors of any kind occurred during build
+ */
+ Success = 1 << 0,
+ /**
+ * None of the .d.ts files emitted by this build were
+ * different from the existing files on disk
+ */
+ DeclarationOutputUnchanged = 1 << 1,
+
+ ConfigFileErrors = 1 << 2,
+ SyntaxErrors = 1 << 3,
+ TypeErrors = 1 << 4,
+ DeclarationEmitErrors = 1 << 5,
+ EmitErrors = 1 << 6,
+
+ AnyErrors = ConfigFileErrors | SyntaxErrors | TypeErrors | DeclarationEmitErrors | EmitErrors
+ }
+
+ /*@internal*/
+ export type ResolvedConfigFilePath = ResolvedConfigFileName & Path;
+ interface FileMap extends Map {
+ get(key: U): T | undefined;
+ has(key: U): boolean;
+ forEach(action: (value: T, key: U) => void): void;
+ readonly size: number;
+ keys(): Iterator;
+ values(): Iterator;
+ entries(): Iterator<[U, T]>;
+ set(key: U, value: T): this;
+ delete(key: U): boolean;
clear(): void;
}
-
- type ResolvedConfigFilePath = ResolvedConfigFileName & Path;
- type ConfigFileMap = FileMap;
- type ToResolvedConfigFilePath = (fileName: ResolvedConfigFileName) => ResolvedConfigFilePath;
- type ToPath = (fileName: string) => Path;
-
- /**
- * A FileMap maintains a normalized-key to value relationship
- */
- function createFileMap(toPath: ToResolvedConfigFilePath): ConfigFileMap;
- function createFileMap(toPath: ToPath): FileMap;
- function createFileMap(toPath: (fileName: U) => V): FileMap {
- // tslint:disable-next-line:no-null-keyword
- const lookup = createMap();
-
- return {
- setValue,
- getValue,
- removeKey,
- forEach,
- hasKey,
- getSize,
- clear
- };
-
- function forEach(action: (value: T, key: V) => void) {
- lookup.forEach(action);
- }
-
- function hasKey(fileName: U) {
- return lookup.has(toPath(fileName));
- }
-
- function removeKey(fileName: U) {
- lookup.delete(toPath(fileName));
- }
-
- function setValue(fileName: U, value: T) {
- lookup.set(toPath(fileName), value);
- }
-
- function getValue(fileName: U): T | undefined {
- return lookup.get(toPath(fileName));
- }
-
- function getSize() {
- return lookup.size;
- }
-
- function clear() {
- lookup.clear();
- }
+ type ConfigFileMap = FileMap;
+ function createConfigFileMap(): ConfigFileMap {
+ return createMap() as ConfigFileMap;
}
- function getOrCreateValueFromConfigFileMap(configFileMap: ConfigFileMap, resolved: ResolvedConfigFileName, createT: () => T): T {
- const existingValue = configFileMap.getValue(resolved);
+ function getOrCreateValueFromConfigFileMap(configFileMap: ConfigFileMap, resolved: ResolvedConfigFilePath, createT: () => T): T {
+ const existingValue = configFileMap.get(resolved);
let newValue: T | undefined;
if (!existingValue) {
newValue = createT();
- configFileMap.setValue(resolved, newValue);
+ configFileMap.set(resolved, newValue);
}
return existingValue || newValue!;
}
- function getOrCreateValueMapFromConfigFileMap(configFileMap: ConfigFileMap