diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000000..1f64bbcee32 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,48 @@ +node_modules +.node_modules +built/* +test-args.txt +~*.docx +\#*\# +.\#* +src/harness/*.js +src/compiler/diagnosticInformationMap.generated.ts +src/compiler/diagnosticMessages.generated.json +src/parser/diagnosticInformationMap.generated.ts +src/parser/diagnosticMessages.generated.json +rwc-report.html +*.swp +build.json +*.actual +*.config +scripts/debug.bat +scripts/run.bat +scripts/word2md.js +scripts/buildProtocol.js +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 +scripts/importDefinitelyTypedTests/importDefinitelyTypedTests.js +scripts/generateLocalizedDiagnosticMessages.js +scripts/*.js.map +scripts/typings/ +coverage/ +internal/ +**/.DS_Store +.settings +**/.vs +.idea +yarn.lock +yarn-error.log +.parallelperf.* +.failed-tests +TEST-results.xml +package-lock.json +tests +.vscode +.git \ No newline at end of file diff --git a/.gitignore b/.gitignore index 9d0bb4d0c6f..a906e2cf7d5 100644 --- a/.gitignore +++ b/.gitignore @@ -34,6 +34,7 @@ tests/webTestServer.js.map tests/webhost/*.d.ts tests/webhost/webtsc.js tests/cases/**/*.js +!tests/cases/docker/*.js/ tests/cases/**/*.js.map *.config scripts/debug.bat @@ -44,6 +45,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/.npmignore b/.npmignore index 482633f48fc..2144451e3e8 100644 --- a/.npmignore +++ b/.npmignore @@ -29,3 +29,5 @@ package-lock.json yarn.lock CONTRIBUTING.md TEST-results.xml +.dockerignore +Dockerfile \ No newline at end of file diff --git a/.yarnrc b/.yarnrc new file mode 100644 index 00000000000..acaaffdb73e --- /dev/null +++ b/.yarnrc @@ -0,0 +1 @@ +--install.no-lockfile true diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000000..8898a69af6d --- /dev/null +++ b/Dockerfile @@ -0,0 +1,7 @@ +# We use this dockerfile to build a packed tarfile which we import in our `docker` tests +FROM node:current +COPY . /typescript +WORKDIR /typescript +RUN npm install +RUN npm i -g gulp-cli +RUN gulp configure-insiders && gulp LKG && gulp clean && npm pack . \ No newline at end of file diff --git a/Gulpfile.js b/Gulpfile.js index bfc4bf43db7..42987e4fd8d 100644 --- a/Gulpfile.js +++ b/Gulpfile.js @@ -415,6 +415,8 @@ task("runtests").flags = { " --no-lint": "Disables lint", " --timeout=": "Overrides the default test timeout.", " --built": "Compile using the built version of the compiler.", + " --shards": "Total number of shards running tests (default: 1)", + " --shardId": "1-based ID of this shard (default: 1)", } const runTestsParallel = () => runConsoleTests("built/local/run.js", "min", /*runInParallel*/ true, /*watchMode*/ false); @@ -429,6 +431,9 @@ task("runtests-parallel").flags = { " --workers=": "The number of parallel workers to use.", " --timeout=": "Overrides the default test timeout.", " --built": "Compile using the built version of the compiler.", + " --skipPercent=": "Skip expensive tests with chance to miss an edit. Default 5%.", + " --shards": "Total number of shards running tests (default: 1)", + " --shardId": "1-based ID of this shard (default: 1)", }; task("diff", () => exec(getDiffTool(), [refBaseline, localBaseline], { ignoreExitCode: true })); diff --git a/README.md b/README.md index 52e35c16f79..04c8c7dbbcc 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,13 @@ + +# TypeScript + +[![Join the chat at https://gitter.im/Microsoft/TypeScript](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/Microsoft/TypeScript?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Build Status](https://travis-ci.org/microsoft/TypeScript.svg?branch=master)](https://travis-ci.org/Microsoft/TypeScript) [![VSTS Build Status](https://dev.azure.com/typescript/TypeScript/_apis/build/status/Typescript/node10)](https://dev.azure.com/typescript/TypeScript/_build/latest?definitionId=4&view=logs) [![npm version](https://badge.fury.io/js/typescript.svg)](https://www.npmjs.com/package/typescript) [![Downloads](https://img.shields.io/npm/dm/typescript.svg)](https://www.npmjs.com/package/typescript) -# TypeScript -[![Join the chat at https://gitter.im/Microsoft/TypeScript](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/Microsoft/TypeScript?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [TypeScript](https://www.typescriptlang.org/) is a language for application-scale JavaScript. TypeScript adds optional types to JavaScript that support tools for large-scale JavaScript applications for any browser, for any host, on any OS. TypeScript compiles to readable, standards-based JavaScript. Try it out at the [playground](https://www.typescriptlang.org/play/), and stay up to date via [our blog](https://blogs.msdn.microsoft.com/typescript) and [Twitter account](https://twitter.com/typescript). diff --git a/package.json b/package.json index a50a79aff13..276a49a53b3 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,11 +76,13 @@ "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", "q": "latest", "remove-internal": "^2.9.2", + "simple-git": "^1.113.0", "source-map-support": "latest", "through2": "latest", "travis-fold": "latest", @@ -99,7 +103,8 @@ "gulp": "gulp", "jake": "gulp", "lint": "gulp lint", - "setup-hooks": "node scripts/link-hooks.js" + "setup-hooks": "node scripts/link-hooks.js", + "update-costly-tests": "node scripts/costly-tests.js" }, "browser": { "fs": false, diff --git a/scripts/build/options.js b/scripts/build/options.js index b7233ba70b0..fecac01dd05 100644 --- a/scripts/build/options.js +++ b/scripts/build/options.js @@ -5,7 +5,7 @@ const os = require("os"); /** @type {CommandLineOptions} */ module.exports = minimist(process.argv.slice(2), { boolean: ["debug", "dirty", "inspect", "light", "colors", "lint", "lkg", "soft", "fix", "failed", "keepFailed", "force", "built"], - string: ["browser", "tests", "host", "reporter", "stackTraceLimit", "timeout"], + string: ["browser", "tests", "host", "reporter", "stackTraceLimit", "timeout", "shards", "shardId"], alias: { "b": "browser", "d": "debug", "debug-brk": "debug", @@ -14,6 +14,7 @@ module.exports = minimist(process.argv.slice(2), { "ru": "runners", "runner": "runners", "r": "reporter", "c": "colors", "color": "colors", + "skip-percent": "skipPercent", "w": "workers", "f": "fix" }, @@ -69,4 +70,4 @@ if (module.exports.built) { * * @typedef {import("minimist").ParsedArgs & TypedOptions} CommandLineOptions */ -void 0; \ No newline at end of file +void 0; diff --git a/scripts/build/tests.js b/scripts/build/tests.js index 0ac65002ab8..20d2a4c7c2a 100644 --- a/scripts/build/tests.js +++ b/scripts/build/tests.js @@ -31,10 +31,13 @@ async function runConsoleTests(runJs, defaultReporter, runInParallel, watchMode, const inspect = cmdLineOptions.inspect; const runners = cmdLineOptions.runners; const light = cmdLineOptions.light; + const skipPercent = process.env.CI === "true" ? 0 : cmdLineOptions.skipPercent; const stackTraceLimit = cmdLineOptions.stackTraceLimit; const testConfigFile = "test.config"; const failed = cmdLineOptions.failed; const keepFailed = cmdLineOptions.keepFailed; + const shards = +cmdLineOptions.shards || undefined; + const shardId = +cmdLineOptions.shardId || undefined; if (!cmdLineOptions.dirty) { await cleanTestDirs(); cancelToken.throwIfCancellationRequested(); @@ -62,8 +65,8 @@ async function runConsoleTests(runJs, defaultReporter, runInParallel, watchMode, testTimeout = 400000; } - if (tests || runners || light || testTimeout || taskConfigsFolder || keepFailed) { - writeTestConfigFile(tests, runners, light, taskConfigsFolder, workerCount, stackTraceLimit, testTimeout, keepFailed); + if (tests || runners || light || testTimeout || taskConfigsFolder || keepFailed || skipPercent !== undefined || shards || shardId) { + writeTestConfigFile(tests, runners, light, skipPercent, taskConfigsFolder, workerCount, stackTraceLimit, testTimeout, keepFailed, shards, shardId); } const colors = cmdLineOptions.colors; @@ -158,23 +161,29 @@ exports.cleanTestDirs = cleanTestDirs; * @param {string} tests * @param {string} runners * @param {boolean} light + * @param {string} skipPercent * @param {string} [taskConfigsFolder] * @param {string | number} [workerCount] * @param {string} [stackTraceLimit] * @param {string | number} [timeout] * @param {boolean} [keepFailed] + * @param {number | undefined} [shards] + * @param {number | undefined} [shardId] */ -function writeTestConfigFile(tests, runners, light, taskConfigsFolder, workerCount, stackTraceLimit, timeout, keepFailed) { +function writeTestConfigFile(tests, runners, light, skipPercent, taskConfigsFolder, workerCount, stackTraceLimit, timeout, keepFailed, shards, shardId) { const testConfigContents = JSON.stringify({ test: tests ? [tests] : undefined, runners: runners ? runners.split(",") : undefined, light, + skipPercent, workerCount, stackTraceLimit, taskConfigsFolder, noColor: !cmdLineOptions.colors, timeout, - keepFailed + keepFailed, + shards, + shardId }); log.info("Running tests with config: " + testConfigContents); fs.writeFileSync("test.config", testConfigContents); diff --git a/scripts/costly-tests.js b/scripts/costly-tests.js new file mode 100644 index 00000000000..a0ee64a6bec --- /dev/null +++ b/scripts/costly-tests.js @@ -0,0 +1,103 @@ +// @ts-check +const fs = require("fs"); +const git = require('simple-git/promise')('.') +const readline = require('readline') + +/** @typedef {{ [s: string]: number}} Histogram */ + +async function main() { + /** @type {Histogram} */ + const edits = Object.create(null) + /** @type {Histogram} */ + const perf = JSON.parse(fs.readFileSync('.parallelperf.json', 'utf8')) + + await collectCommits(git, "release-2.3", "master", /*author*/ undefined, files => fillMap(files, edits)) + + const totalTime = Object.values(perf).reduce((n,m) => n + m, 0) + const untouched = Object.values(perf).length - Object.values(edits).length + const totalEdits = Object.values(edits).reduce((n,m) => n + m, 0) + untouched + Object.values(edits).length + + let i = 0 + /** @type {{ name: string, time: number, edits: number, cost: number }[]} */ + let data = [] + for (const k in perf) { + const otherk = k.replace(/tsrunner-[a-z-]+?:\/\//, '') + const percentTime = perf[k] / totalTime + const percentHits = (1 + (edits[otherk] || 0)) / totalEdits + const cost = 5 + Math.log(percentTime / percentHits) + data.push({ name: otherk, time: perf[k], edits: 1 + (edits[otherk] || 0), cost}) + if (edits[otherk]) + i++ + } + const output = { + totalTime, + totalEdits, + data: data.sort((x,y) => y.cost - x.cost).map(x => ({ ...x, cost: x.cost.toFixed(2) })) + } + + fs.writeFileSync('tests/.test-cost.json', JSON.stringify(output), 'utf8') +} + +main().catch(e => { + console.log(e); + process.exit(1); +}) + +/** + * @param {string[]} files + * @param {Histogram} histogram + */ +function fillMap(files, histogram) { + // keep edits to test cases (but not /users), and not file moves + const tests = files.filter(f => f.startsWith('tests/cases/') && !f.startsWith('tests/cases/user') && !/=>/.test(f)) + for (const test of tests) { + histogram[test] = (histogram[test] || 0) + 1 + } +} + +/** + * @param {string} s + */ +function isSquashMergeMessage(s) { + return /\(#[0-9]+\)$/.test(s) +} + +/** + * @param {string} s + */ +function isMergeCommit(s) { + return /Merge pull request #[0-9]+/.test(s) +} + +/** + * @param {string} s + */ +function parseFiles(s) { + const lines = s.split('\n') + // Note that slice(2) only works for merge commits, which have an empty newline after the title + return lines.slice(2, lines.length - 2).map(line => line.split("|")[0].trim()) +} + +/** + * @param {import('simple-git/promise').SimpleGit} git + * @param {string} from + * @param {string} to + * @param {string | undefined} author - only include commits from this author + * @param {(files: string[]) => void} update + */ + async function collectCommits(git, from, to, author, update) { + let i = 0 + for (const commit of (await git.log({ from, to })).all) { + i++ + if ((!author || commit.author_name === author) && isMergeCommit(commit.message) || isSquashMergeMessage(commit.message)) { + readline.clearLine(process.stdout, /*left*/ -1) + readline.cursorTo(process.stdout, 0) + process.stdout.write(i + ": " + commit.date) + const files = parseFiles(await git.show([commit.hash, "--stat=1000,960,40", "--pretty=oneline"])) + update(files) + } + } +} + + + diff --git a/scripts/open-cherry-pick-pr.ts b/scripts/open-cherry-pick-pr.ts new file mode 100644 index 00000000000..5fc7f0ae299 --- /dev/null +++ b/scripts/open-cherry-pick-pr.ts @@ -0,0 +1,106 @@ +/// +// 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"); + const mergebase = runSequence([["git", ["merge-base", "origin/master", currentSha]]]).trim(); + runSequence([ + ["git", ["checkout", "-b", "temp-branch"]], + ["git", ["reset", mergebase, "--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..1959ee4aaa0 100644 --- a/scripts/open-user-pr.ts +++ b/scripts/open-user-pr.ts @@ -1,4 +1,5 @@ /// +/// // Must reference esnext.asynciterable lib, since octokit uses AsyncIterable internally import Octokit = require("@octokit/rest"); import {runSequence} from "./run-sequence"; @@ -9,7 +10,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`; @@ -29,31 +30,31 @@ gh.authenticate({ token: process.argv[2] }); gh.pulls.create({ - owner: process.env.TARGET_FORK, + owner: process.env.TARGET_FORK!, repo: "TypeScript", maintainer_can_modify: true, title: `🤖 User test baselines have changed` + (process.env.TARGET_BRANCH ? ` for ${process.env.TARGET_BRANCH}` : ""), 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, + owner: process.env.TARGET_FORK!, repo: "TypeScript", - number: num, + pull_number: num, reviewers, }); } 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..3c25e2f2dc6 100644 --- a/scripts/update-experimental-branches.js +++ b/scripts/update-experimental-branches.js @@ -1,87 +1,92 @@ // @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 ` + * 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...`); - } - console.log(`Performing experimental branch updating and merging for branches ${branchesRaw}`); - - const gh = new Octokit(); - gh.authenticate({ - type: "token", - token: process.argv[2] + const gh = new Octokit({ + auth: process.argv[2] }); - - // Fetch all relevant refs - runSequence([ - ["git", ["fetch", "origin", "master:master", ...branches.map(b => `${b}:${b}`)]] - ]) - + const prnums = (await gh.issues.listForRepo({ + labels: "typescript@experimental", + sort: "created", + state: "open", + owner: "Microsoft", + repo: "TypeScript", + })).data.filter(i => !!i.pull_request).map(i => i.number); + if (triggeredPR && !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`; + // 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.` - }); + + 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`); // 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 +95,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 96db98d337a..38d5ed24eae 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -274,6 +274,9 @@ namespace ts { if (isStringOrNumericLiteralLike(nameExpression)) { return escapeLeadingUnderscores(nameExpression.text); } + if (isSignedNumericLiteral(nameExpression)) { + return tokenToString(nameExpression.operator) + nameExpression.operand.text as __String; + } Debug.assert(isWellKnownSymbolSyntactically(nameExpression)); return getPropertyNameForKnownSymbolName(idText((nameExpression).name)); @@ -2515,7 +2518,7 @@ namespace ts { break; default: - Debug.fail(Debug.showSyntaxKind(thisContainer)); + Debug.failBadSyntaxKind(thisContainer); } } @@ -2581,7 +2584,7 @@ namespace ts { // Fix up parent pointers since we're going to use these nodes before we bind into them node.left.parent = node; node.right.parent = node; - if (isIdentifier(lhs.expression) && container === file && isNameOfExportsOrModuleExportsAliasDeclaration(file, lhs.expression)) { + if (isIdentifier(lhs.expression) && container === file && isExportsOrModuleExportsOrAlias(file, lhs.expression)) { // This can be an alias for the 'exports' or 'module.exports' names, e.g. // var util = module.exports; // util.property = function ... @@ -2975,21 +2978,27 @@ namespace ts { } export function isExportsOrModuleExportsOrAlias(sourceFile: SourceFile, node: Expression): boolean { - return isExportsIdentifier(node) || - isModuleExportsPropertyAccessExpression(node) || - isIdentifier(node) && isNameOfExportsOrModuleExportsAliasDeclaration(sourceFile, node); - } - - function isNameOfExportsOrModuleExportsAliasDeclaration(sourceFile: SourceFile, node: Identifier): boolean { - const symbol = lookupSymbolForNameWorker(sourceFile, node.escapedText); - return !!symbol && !!symbol.valueDeclaration && isVariableDeclaration(symbol.valueDeclaration) && - !!symbol.valueDeclaration.initializer && isExportsOrModuleExportsOrAliasOrAssignment(sourceFile, symbol.valueDeclaration.initializer); - } - - function isExportsOrModuleExportsOrAliasOrAssignment(sourceFile: SourceFile, node: Expression): boolean { - return isExportsOrModuleExportsOrAlias(sourceFile, node) || - (isAssignmentExpression(node, /*excludeCompoundAssignment*/ true) && ( - isExportsOrModuleExportsOrAliasOrAssignment(sourceFile, node.left) || isExportsOrModuleExportsOrAliasOrAssignment(sourceFile, node.right))); + let i = 0; + const q = [node]; + while (q.length && i < 100) { + i++; + node = q.shift()!; + if (isExportsIdentifier(node) || isModuleExportsPropertyAccessExpression(node)) { + return true; + } + else if (isIdentifier(node)) { + const symbol = lookupSymbolForNameWorker(sourceFile, node.escapedText); + if (!!symbol && !!symbol.valueDeclaration && isVariableDeclaration(symbol.valueDeclaration) && !!symbol.valueDeclaration.initializer) { + const init = symbol.valueDeclaration.initializer; + q.push(init); + if (isAssignmentExpression(init, /*excludeCompoundAssignment*/ true)) { + q.push(init.left); + q.push(init.right); + } + } + } + } + return false; } function lookupSymbolForNameWorker(container: Node, name: __String): Symbol | undefined { @@ -3223,8 +3232,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) @@ -3241,8 +3249,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; @@ -3332,7 +3339,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; } @@ -3363,7 +3369,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; } @@ -3378,12 +3383,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..704cdaf23f9 100644 --- a/src/compiler/builder.ts +++ b/src/compiler/builder.ts @@ -10,18 +10,13 @@ namespace ts { export interface ReusableDiagnosticRelatedInformation { category: DiagnosticCategory; code: number; - file: Path | undefined; + file: string | undefined; start: number | undefined; length: number | undefined; messageText: string | ReusableDiagnosticMessageChain; } - export interface ReusableDiagnosticMessageChain { - messageText: string; - category: DiagnosticCategory; - code: number; - next?: ReusableDiagnosticMessageChain; - } + export type ReusableDiagnosticMessageChain = DiagnosticMessageChain; export interface ReusableBuilderProgramState extends ReusableBuilderState { /** @@ -227,7 +222,7 @@ namespace ts { // Unchanged file copy diagnostics const diagnostics = oldState!.semanticDiagnosticsPerFile!.get(sourceFilePath); if (diagnostics) { - state.semanticDiagnosticsPerFile!.set(sourceFilePath, oldState!.hasReusableDiagnostic ? convertToDiagnostics(diagnostics as ReadonlyArray, newProgram) : diagnostics as ReadonlyArray); + state.semanticDiagnosticsPerFile!.set(sourceFilePath, oldState!.hasReusableDiagnostic ? convertToDiagnostics(diagnostics as ReadonlyArray, newProgram, getCanonicalFileName) : diagnostics as ReadonlyArray); if (!state.semanticDiagnosticsFromOldState) { state.semanticDiagnosticsFromOldState = createMap(); } @@ -246,37 +241,32 @@ namespace ts { return state; } - function convertToDiagnostics(diagnostics: ReadonlyArray, newProgram: Program): ReadonlyArray { + function convertToDiagnostics(diagnostics: ReadonlyArray, newProgram: Program, getCanonicalFileName: GetCanonicalFileName): ReadonlyArray { if (!diagnostics.length) return emptyArray; + const buildInfoDirectory = getDirectoryPath(getNormalizedAbsolutePath(getOutputPathForBuildInfo(newProgram.getCompilerOptions())!, newProgram.getCurrentDirectory())); return diagnostics.map(diagnostic => { - const result: Diagnostic = convertToDiagnosticRelatedInformation(diagnostic, newProgram); + const result: Diagnostic = convertToDiagnosticRelatedInformation(diagnostic, newProgram, toPath); result.reportsUnnecessary = diagnostic.reportsUnnecessary; result.source = diagnostic.source; const { relatedInformation } = diagnostic; result.relatedInformation = relatedInformation ? relatedInformation.length ? - relatedInformation.map(r => convertToDiagnosticRelatedInformation(r, newProgram)) : + relatedInformation.map(r => convertToDiagnosticRelatedInformation(r, newProgram, toPath)) : emptyArray : undefined; return result; }); + + function toPath(path: string) { + return ts.toPath(path, buildInfoDirectory, getCanonicalFileName); + } } - function convertToDiagnosticRelatedInformation(diagnostic: ReusableDiagnosticRelatedInformation, newProgram: Program): DiagnosticRelatedInformation { - const { file, messageText } = diagnostic; + function convertToDiagnosticRelatedInformation(diagnostic: ReusableDiagnosticRelatedInformation, newProgram: Program, toPath: (path: string) => Path): DiagnosticRelatedInformation { + const { file } = diagnostic; return { ...diagnostic, - file: file && newProgram.getSourceFileByPath(file), - messageText: messageText === undefined || isString(messageText) ? - messageText : - convertToDiagnosticMessageChain(messageText, newProgram) - }; - } - - function convertToDiagnosticMessageChain(diagnostic: ReusableDiagnosticMessageChain, newProgram: Program): DiagnosticMessageChain { - return { - ...diagnostic, - next: diagnostic.next && convertToDiagnosticMessageChain(diagnostic.next, newProgram) + file: file ? newProgram.getSourceFileByPath(toPath(file)) : undefined }; } @@ -620,19 +610,23 @@ namespace ts { /** * Gets the program information to be emitted in buildInfo so that we can use it to create new program */ - function getProgramBuildInfo(state: Readonly): ProgramBuildInfo | undefined { + function getProgramBuildInfo(state: Readonly, getCanonicalFileName: GetCanonicalFileName): ProgramBuildInfo | undefined { if (state.compilerOptions.outFile || state.compilerOptions.out) return undefined; + const buildInfoDirectory = getDirectoryPath(getNormalizedAbsolutePath(getOutputPathForBuildInfo(state.compilerOptions)!, Debug.assertDefined(state.program).getCurrentDirectory())); const fileInfos: MapLike = {}; state.fileInfos.forEach((value, key) => { const signature = state.currentAffectedFilesSignatures && state.currentAffectedFilesSignatures.get(key); - fileInfos[key] = signature === undefined ? value : { version: value.version, signature }; + fileInfos[relativeToBuildInfo(key)] = signature === undefined ? value : { version: value.version, signature }; }); - const result: ProgramBuildInfo = { fileInfos, options: state.compilerOptions }; + const result: ProgramBuildInfo = { + fileInfos, + options: convertToReusableCompilerOptions(state.compilerOptions, relativeToBuildInfo) + }; if (state.referencedMap) { const referencedMap: MapLike = {}; state.referencedMap.forEach((value, key) => { - referencedMap[key] = arrayFrom(value.keys()); + referencedMap[relativeToBuildInfo(key)] = arrayFrom(value.keys(), relativeToBuildInfo); }); result.referencedMap = referencedMap; } @@ -642,9 +636,9 @@ namespace ts { state.exportedModulesMap.forEach((value, key) => { const newValue = state.currentAffectedFilesExportedModulesMap && state.currentAffectedFilesExportedModulesMap.get(key); // Not in temporary cache, use existing value - if (newValue === undefined) exportedModulesMap[key] = arrayFrom(value.keys()); + if (newValue === undefined) exportedModulesMap[relativeToBuildInfo(key)] = arrayFrom(value.keys(), relativeToBuildInfo); // Value in cache and has updated value map, use that - else if (newValue) exportedModulesMap[key] = arrayFrom(newValue.keys()); + else if (newValue) exportedModulesMap[relativeToBuildInfo(key)] = arrayFrom(newValue.keys(), relativeToBuildInfo); }); result.exportedModulesMap = exportedModulesMap; } @@ -655,50 +649,78 @@ namespace ts { state.semanticDiagnosticsPerFile.forEach((value, key) => semanticDiagnosticsPerFile.push( value.length ? [ - key, + relativeToBuildInfo(key), state.hasReusableDiagnostic ? value as ReadonlyArray : - convertToReusableDiagnostics(value as ReadonlyArray) + convertToReusableDiagnostics(value as ReadonlyArray, relativeToBuildInfo) ] : - key + relativeToBuildInfo(key) )); result.semanticDiagnosticsPerFile = semanticDiagnosticsPerFile; } return result; + + function relativeToBuildInfo(path: string) { + return ensurePathIsNonModuleName(getRelativePathFromDirectory(buildInfoDirectory, path, getCanonicalFileName)); + } } - function convertToReusableDiagnostics(diagnostics: ReadonlyArray): ReadonlyArray { + function convertToReusableCompilerOptions(options: CompilerOptions, relativeToBuildInfo: (path: string) => string) { + const result: CompilerOptions = {}; + const optionsNameMap = getOptionNameMap().optionNameMap; + + for (const name in options) { + if (hasProperty(options, name)) { + result[name] = convertToReusableCompilerOptionValue( + optionsNameMap.get(name.toLowerCase()), + options[name] as CompilerOptionsValue, + relativeToBuildInfo + ); + } + } + if (result.configFilePath) { + result.configFilePath = relativeToBuildInfo(result.configFilePath); + } + return result; + } + + function convertToReusableCompilerOptionValue(option: CommandLineOption | undefined, value: CompilerOptionsValue, relativeToBuildInfo: (path: string) => string) { + if (option) { + if (option.type === "list") { + const values = value as ReadonlyArray; + if (option.element.isFilePath && values.length) { + return values.map(relativeToBuildInfo); + } + } + else if (option.isFilePath) { + return relativeToBuildInfo(value as string); + } + } + return value; + } + + function convertToReusableDiagnostics(diagnostics: ReadonlyArray, relativeToBuildInfo: (path: string) => string): ReadonlyArray { Debug.assert(!!diagnostics.length); return diagnostics.map(diagnostic => { - const result: ReusableDiagnostic = convertToReusableDiagnosticRelatedInformation(diagnostic); + const result: ReusableDiagnostic = convertToReusableDiagnosticRelatedInformation(diagnostic, relativeToBuildInfo); result.reportsUnnecessary = diagnostic.reportsUnnecessary; result.source = diagnostic.source; const { relatedInformation } = diagnostic; result.relatedInformation = relatedInformation ? relatedInformation.length ? - relatedInformation.map(r => convertToReusableDiagnosticRelatedInformation(r)) : + relatedInformation.map(r => convertToReusableDiagnosticRelatedInformation(r, relativeToBuildInfo)) : emptyArray : undefined; return result; }); } - function convertToReusableDiagnosticRelatedInformation(diagnostic: DiagnosticRelatedInformation): ReusableDiagnosticRelatedInformation { - const { file, messageText } = diagnostic; + function convertToReusableDiagnosticRelatedInformation(diagnostic: DiagnosticRelatedInformation, relativeToBuildInfo: (path: string) => string): ReusableDiagnosticRelatedInformation { + const { file } = diagnostic; return { ...diagnostic, - file: file && file.path, - messageText: messageText === undefined || isString(messageText) ? - messageText : - convertToReusableDiagnosticMessageChain(messageText) - }; - } - - function convertToReusableDiagnosticMessageChain(diagnostic: DiagnosticMessageChain): ReusableDiagnosticMessageChain { - return { - ...diagnostic, - next: diagnostic.next && convertToReusableDiagnosticMessageChain(diagnostic.next) + file: file ? relativeToBuildInfo(file.path) : undefined }; } @@ -767,7 +789,7 @@ namespace ts { const computeHash = host.createHash || generateDjb2Hash; let state = createBuilderProgramState(newProgram, getCanonicalFileName, oldState); let backupState: BuilderProgramState | undefined; - newProgram.getProgramBuildInfo = () => getProgramBuildInfo(state); + newProgram.getProgramBuildInfo = () => getProgramBuildInfo(state, getCanonicalFileName); // To ensure that we arent storing any references to old program or new program without state newProgram = undefined!; // TODO: GH#18217 @@ -796,6 +818,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 +936,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 +979,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; @@ -984,26 +1002,35 @@ namespace ts { } } - function getMapOfReferencedSet(mapLike: MapLike> | undefined): ReadonlyMap | undefined { + function getMapOfReferencedSet(mapLike: MapLike> | undefined, toPath: (path: string) => Path): ReadonlyMap | undefined { if (!mapLike) return undefined; const map = createMap(); // Copies keys/values from template. Note that for..in will not throw if // template is undefined, and instead will just exit the loop. for (const key in mapLike) { if (hasProperty(mapLike, key)) { - map.set(key, arrayToSet(mapLike[key])); + map.set(toPath(key), arrayToSet(mapLike[key], toPath)); } } return map; } - export function createBuildProgramUsingProgramBuildInfo(program: ProgramBuildInfo): EmitAndSemanticDiagnosticsBuilderProgram & SemanticDiagnosticsBuilderProgram { - const fileInfos = createMapFromTemplate(program.fileInfos); + export function createBuildProgramUsingProgramBuildInfo(program: ProgramBuildInfo, buildInfoPath: string, host: ReadBuildProgramHost): EmitAndSemanticDiagnosticsBuilderProgram { + const buildInfoDirectory = getDirectoryPath(getNormalizedAbsolutePath(buildInfoPath, host.getCurrentDirectory())); + const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames()); + + const fileInfos = createMap(); + for (const key in program.fileInfos) { + if (hasProperty(program.fileInfos, key)) { + fileInfos.set(toPath(key), program.fileInfos[key]); + } + } + const state: ReusableBuilderProgramState = { fileInfos, - compilerOptions: program.options, - referencedMap: getMapOfReferencedSet(program.referencedMap), - exportedModulesMap: getMapOfReferencedSet(program.exportedModulesMap), + compilerOptions: convertFromReusableCompilerOptions(program.options, toAbsolutePath), + referencedMap: getMapOfReferencedSet(program.referencedMap, toPath), + exportedModulesMap: getMapOfReferencedSet(program.exportedModulesMap, toPath), semanticDiagnosticsPerFile: program.semanticDiagnosticsPerFile && arrayToMap(program.semanticDiagnosticsPerFile, value => isString(value) ? value : value[0], value => isString(value) ? emptyArray : value[1]), hasReusableDiagnostic: true }; @@ -1029,6 +1056,48 @@ namespace ts { emitNextAffectedFile: notImplemented, getSemanticDiagnosticsOfNextAffectedFile: notImplemented, }; + + function toPath(path: string) { + return ts.toPath(path, buildInfoDirectory, getCanonicalFileName); + } + + function toAbsolutePath(path: string) { + return getNormalizedAbsolutePath(path, buildInfoDirectory); + } + } + + function convertFromReusableCompilerOptions(options: CompilerOptions, toAbsolutePath: (path: string) => string) { + const result: CompilerOptions = {}; + const optionsNameMap = getOptionNameMap().optionNameMap; + + for (const name in options) { + if (hasProperty(options, name)) { + result[name] = convertFromReusableCompilerOptionValue( + optionsNameMap.get(name.toLowerCase()), + options[name] as CompilerOptionsValue, + toAbsolutePath + ); + } + } + if (result.configFilePath) { + result.configFilePath = toAbsolutePath(result.configFilePath); + } + return result; + } + + function convertFromReusableCompilerOptionValue(option: CommandLineOption | undefined, value: CompilerOptionsValue, toAbsolutePath: (path: string) => string) { + if (option) { + if (option.type === "list") { + const values = value as ReadonlyArray; + if (option.element.isFilePath && values.length) { + return values.map(toAbsolutePath); + } + } + else if (option.isFilePath) { + return toAbsolutePath(value as string); + } + } + return value; } export function createRedirectedBuilderProgram(state: { program: Program | undefined; compilerOptions: CompilerOptions; }, configFileParsingDiagnostics: ReadonlyArray): BuilderProgram { @@ -1181,7 +1250,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 764f926ecdb..92df91d0084 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; @@ -391,10 +395,9 @@ namespace ts { const tupleTypes = createMap(); const unionTypes = createMap(); - const intersectionTypes = createMap(); + const intersectionTypes = createMap(); const literalTypes = createMap(); const indexedAccessTypes = createMap(); - const conditionalTypes = createMap(); const substitutionTypes = createMap(); const evolvingArrayTypes: EvolvingArrayType[] = []; const undefinedProperties = createMap() as UnderscoreEscapedMap; @@ -559,6 +562,8 @@ namespace ts { const symbolLinks: SymbolLinks[] = []; const nodeLinks: NodeLinks[] = []; const flowLoopCaches: Map[] = []; + const flowAssignmentKeys: string[] = []; + const flowAssignmentTypes: FlowType[] = []; const flowLoopNodes: FlowNode[] = []; const flowLoopKeys: string[] = []; const flowLoopTypes: Type[][] = []; @@ -3260,7 +3265,7 @@ namespace ts { function hasVisibleDeclarations(symbol: Symbol, shouldComputeAliasToMakeVisible: boolean): SymbolVisibilityResult | undefined { let aliasesToMakeVisible: LateVisibilityPaintedStatement[] | undefined; - if (!every(symbol.declarations, getIsDeclarationVisible)) { + if (!every(filter(symbol.declarations, d => d.kind !== SyntaxKind.Identifier), getIsDeclarationVisible)) { return undefined; } return { accessibility: SymbolAccessibility.Accessible, aliasesToMakeVisible }; @@ -3562,7 +3567,7 @@ namespace ts { context.approximateLength += 6; return createKeywordTypeNode(SyntaxKind.ObjectKeyword); } - if (type.flags & TypeFlags.TypeParameter && (type as TypeParameter).isThisType) { + if (isThisTypeParameter(type)) { if (context.flags & NodeBuilderFlags.InObjectTypeLiteral) { if (!context.encounteredError && !(context.flags & NodeBuilderFlags.AllowThisInObjectLiteral)) { context.encounteredError = true; @@ -3646,8 +3651,8 @@ namespace ts { context.inferTypeParameters = (type).root.inferTypeParameters; const extendsTypeNode = typeToTypeNodeHelper((type).extendsType, context); context.inferTypeParameters = saveInferTypeParameters; - const trueTypeNode = typeToTypeNodeHelper((type).trueType, context); - const falseTypeNode = typeToTypeNodeHelper((type).falseType, context); + const trueTypeNode = typeToTypeNodeHelper(getTrueTypeFromConditionalType(type), context); + const falseTypeNode = typeToTypeNodeHelper(getFalseTypeFromConditionalType(type), context); context.approximateLength += 15; return createConditionalTypeNode(checkTypeNode, extendsTypeNode, trueTypeNode, falseTypeNode); } @@ -4301,6 +4306,11 @@ namespace ts { function lookupTypeParameterNodes(chain: Symbol[], index: number, context: NodeBuilderContext) { Debug.assert(chain && 0 <= index && index < chain.length); const symbol = chain[index]; + const symbolId = "" + getSymbolId(symbol); + if (context.typeParameterSymbolList && context.typeParameterSymbolList.get(symbolId)) { + return undefined; + } + (context.typeParameterSymbolList || (context.typeParameterSymbolList = createMap())).set(symbolId, true); let typeParameterNodes: ReadonlyArray | ReadonlyArray | undefined; if (context.flags & NodeBuilderFlags.WriteTypeParametersInQualifiedName && index < (chain.length - 1)) { const parentSymbol = symbol; @@ -4627,6 +4637,7 @@ namespace ts { inferTypeParameters: TypeParameter[] | undefined; approximateLength: number; truncating?: boolean; + typeParameterSymbolList?: Map; } function isDefaultBindingContext(location: Node) { @@ -4641,6 +4652,9 @@ namespace ts { if (!isIdentifierText(name, compilerOptions.target) && !isNumericLiteralName(name)) { return `"${escapeString(name, CharacterCodes.doubleQuote)}"`; } + if (isNumericLiteralName(name) && startsWith(name, "-")) { + return `[${name}]`; + } return name; } if (nameType.flags & TypeFlags.UniqueESSymbol) { @@ -5220,7 +5234,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); } @@ -5345,7 +5359,7 @@ namespace ts { return type; } else if (declaredType !== errorType && type !== errorType && !isTypeIdenticalTo(declaredType, type)) { - errorNextVariableOrPropertyDeclarationMustHaveSameType(declaredType, declaration, type); + errorNextVariableOrPropertyDeclarationMustHaveSameType(/*firstDeclaration*/ undefined, declaredType, declaration, type); } } return declaredType; @@ -5470,7 +5484,7 @@ namespace ts { function getTypeFromObjectBindingPattern(pattern: ObjectBindingPattern, includePatternInType: boolean, reportErrors: boolean): Type { const members = createSymbolTable(); let stringIndexInfo: IndexInfo | undefined; - let objectFlags = ObjectFlags.ObjectLiteral | ObjectFlags.ContainsObjectLiteral; + let objectFlags = ObjectFlags.ObjectLiteral | ObjectFlags.ContainsObjectOrArrayLiteral; forEach(pattern.elements, e => { const name = e.propertyName || e.name; if (e.dotDotDotToken) { @@ -5688,7 +5702,7 @@ namespace ts { type = getTypeOfEnumMember(symbol); } else { - return Debug.fail("Unhandled declaration kind! " + Debug.showSyntaxKind(declaration) + " for " + Debug.showSymbol(symbol)); + return Debug.fail("Unhandled declaration kind! " + Debug.formatSyntaxKind(declaration.kind) + " for " + Debug.formatSymbol(symbol)); } if (!popTypeResolution()) { @@ -5910,7 +5924,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); } @@ -6420,7 +6447,8 @@ namespace ts { for (const declaration of symbol.declarations) { if (declaration.kind === SyntaxKind.EnumDeclaration) { for (const member of (declaration).members) { - const memberType = getFreshTypeOfLiteralType(getLiteralType(getEnumMemberValue(member)!, enumCount, getSymbolOfNode(member))); // TODO: GH#18217 + const value = getEnumMemberValue(member); + const memberType = getFreshTypeOfLiteralType(getLiteralType(value !== undefined ? value : 0, enumCount, getSymbolOfNode(member))); getSymbolLinks(getSymbolOfNode(member)).declaredType = memberType; memberTypeList.push(getRegularTypeOfLiteralType(memberType)); } @@ -7045,10 +7073,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; @@ -7209,8 +7237,8 @@ namespace ts { function resolveIntersectionTypeMembers(type: IntersectionType) { // The members and properties collections are empty for intersection types. To get all properties of an // intersection type use getPropertiesOfType (only the language service uses this). - let callSignatures: ReadonlyArray = emptyArray; - let constructSignatures: ReadonlyArray = emptyArray; + let callSignatures: Signature[] | undefined; + let constructSignatures: Signature[] | undefined; let stringIndexInfo: IndexInfo | undefined; let numberIndexInfo: IndexInfo | undefined; const types = type.types; @@ -7232,13 +7260,22 @@ namespace ts { return clone; }); } - constructSignatures = concatenate(constructSignatures, signatures); + constructSignatures = appendSignatures(constructSignatures, signatures); } - callSignatures = concatenate(callSignatures, getSignaturesOfType(t, SignatureKind.Call)); + callSignatures = appendSignatures(callSignatures, getSignaturesOfType(t, SignatureKind.Call)); stringIndexInfo = intersectIndexInfos(stringIndexInfo, getIndexInfoOfType(t, IndexKind.String)); numberIndexInfo = intersectIndexInfos(numberIndexInfo, getIndexInfoOfType(t, IndexKind.Number)); } - setStructuredTypeMembers(type, emptySymbols, callSignatures, constructSignatures, stringIndexInfo, numberIndexInfo); + setStructuredTypeMembers(type, emptySymbols, callSignatures || emptyArray, constructSignatures || emptyArray, stringIndexInfo, numberIndexInfo); + } + + function appendSignatures(signatures: Signature[] | undefined, newSignatures: readonly Signature[]) { + for (const sig of newSignatures) { + if (!signatures || every(signatures, s => !compareSignaturesIdentical(s, sig, /*partialMatch*/ false, /*ignoreThisTypes*/ false, /*ignoreReturnTypes*/ false, compareTypesIdentical))) { + signatures = append(signatures, sig); + } + } + return signatures; } /** @@ -7292,7 +7329,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 @@ -7428,8 +7466,9 @@ namespace ts { else if (t.flags & (TypeFlags.Any | TypeFlags.String)) { stringIndexInfo = createIndexInfo(propType, !!(templateModifiers & MappedTypeModifiers.IncludeReadonly)); } - else if (t.flags & TypeFlags.Number) { - numberIndexInfo = createIndexInfo(propType, !!(templateModifiers & MappedTypeModifiers.IncludeReadonly)); + else if (t.flags & (TypeFlags.Number | TypeFlags.Enum)) { + numberIndexInfo = createIndexInfo(numberIndexInfo ? getUnionType([numberIndexInfo.type, propType]) : propType, + !!(templateModifiers & MappedTypeModifiers.IncludeReadonly)); } } } @@ -7650,15 +7689,20 @@ namespace ts { return hasNonCircularBaseConstraint(type) ? getConstraintFromIndexedAccess(type) : undefined; } + function getSimplifiedTypeOrConstraint(type: Type) { + const simplified = getSimplifiedType(type, /*writing*/ false); + return simplified !== type ? simplified : getConstraintOfType(type); + } + function getConstraintFromIndexedAccess(type: IndexedAccessType) { - const indexConstraint = getConstraintOfType(type.indexType); + const indexConstraint = getSimplifiedTypeOrConstraint(type.indexType); if (indexConstraint && indexConstraint !== type.indexType) { const indexedAccess = getIndexedAccessTypeOrUndefined(type.objectType, indexConstraint); if (indexedAccess) { return indexedAccess; } } - const objectConstraint = getConstraintOfType(type.objectType); + const objectConstraint = getSimplifiedTypeOrConstraint(type.objectType); if (objectConstraint && objectConstraint !== type.objectType) { return getIndexedAccessTypeOrUndefined(objectConstraint, type.indexType); } @@ -7672,7 +7716,7 @@ namespace ts { // just `any`. This result is _usually_ unwanted - so instead here we elide an `any` branch from the constraint type, // in effect treating `any` like `never` rather than `unknown` in this location. const trueConstraint = getInferredTrueTypeFromConditionalType(type); - const falseConstraint = type.falseType; + const falseConstraint = getFalseTypeFromConditionalType(type); type.resolvedDefaultConstraint = isTypeAny(trueConstraint) ? falseConstraint : isTypeAny(falseConstraint) ? trueConstraint : getUnionType([trueConstraint, falseConstraint]); } return type.resolvedDefaultConstraint; @@ -7987,10 +8031,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; } @@ -8045,7 +8092,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; } @@ -8154,6 +8209,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); } @@ -9043,7 +9101,7 @@ namespace ts { } function getSubstitutionType(typeVariable: TypeVariable, substitute: Type) { - if (substitute.flags & TypeFlags.AnyOrUnknown) { + if (substitute.flags & TypeFlags.AnyOrUnknown || substitute === typeVariable) { return typeVariable; } const id = `${getTypeId(typeVariable)}>${getTypeId(substitute)}`; @@ -9476,44 +9534,13 @@ namespace ts { return false; } - // Return true if the given intersection type contains - // more than one unit type or, - // an object type and a nullable type (null or undefined), or - // a string-like type and a type known to be non-string-like, or - // a number-like type and a type known to be non-number-like, or - // a symbol-like type and a type known to be non-symbol-like, or - // a void-like type and a type known to be non-void-like, or - // a non-primitive type and a type known to be primitive. - function isEmptyIntersectionType(type: IntersectionType) { - let combined: TypeFlags = 0; - for (const t of type.types) { - if (t.flags & TypeFlags.Unit && combined & TypeFlags.Unit) { - return true; - } - combined |= t.flags; - if (combined & TypeFlags.Nullable && combined & (TypeFlags.Object | TypeFlags.NonPrimitive) || - combined & TypeFlags.NonPrimitive && combined & (TypeFlags.DisjointDomains & ~TypeFlags.NonPrimitive) || - combined & TypeFlags.StringLike && combined & (TypeFlags.DisjointDomains & ~TypeFlags.StringLike) || - combined & TypeFlags.NumberLike && combined & (TypeFlags.DisjointDomains & ~TypeFlags.NumberLike) || - combined & TypeFlags.BigIntLike && combined & (TypeFlags.DisjointDomains & ~TypeFlags.BigIntLike) || - combined & TypeFlags.ESSymbolLike && combined & (TypeFlags.DisjointDomains & ~TypeFlags.ESSymbolLike) || - combined & TypeFlags.VoidLike && combined & (TypeFlags.DisjointDomains & ~TypeFlags.VoidLike)) { - return true; - } - } - return false; - } - function addTypeToUnion(typeSet: Type[], includes: TypeFlags, type: Type) { const flags = type.flags; if (flags & TypeFlags.Union) { return addTypesToUnion(typeSet, includes, (type).types); } - // We ignore 'never' types in unions. Likewise, we ignore intersections of unit types as they are - // another form of 'never' (in that they have an empty value domain). We could in theory turn - // intersections of unit types into 'never' upon construction, but deferring the reduction makes it - // easier to reason about their origin. - if (!(flags & TypeFlags.Never || flags & TypeFlags.Intersection && isEmptyIntersectionType(type))) { + // We ignore 'never' types in unions + if (!(flags & TypeFlags.Never)) { includes |= flags & TypeFlags.IncludesMask; if (flags & TypeFlags.StructuredOrInstantiable) includes |= TypeFlags.IncludesStructuredOrInstantiable; if (type === wildcardType) includes |= TypeFlags.IncludesWildcard; @@ -9737,13 +9764,18 @@ namespace ts { } } else { - includes |= flags & TypeFlags.IncludesMask; if (flags & TypeFlags.AnyOrUnknown) { if (type === wildcardType) includes |= TypeFlags.IncludesWildcard; } else if ((strictNullChecks || !(flags & TypeFlags.Nullable)) && !contains(typeSet, type)) { + if (type.flags & TypeFlags.Unit && includes & TypeFlags.Unit) { + // We have seen two distinct unit types which means we should reduce to an + // empty intersection. Adding TypeFlags.NonPrimitive causes that to happen. + includes |= TypeFlags.NonPrimitive; + } typeSet.push(type); } + includes |= flags & TypeFlags.IncludesMask; } return includes; } @@ -9837,6 +9869,15 @@ namespace ts { return true; } + function createIntersectionType(types: Type[], aliasSymbol?: Symbol, aliasTypeArguments?: ReadonlyArray) { + const result = createType(TypeFlags.Intersection); + result.objectFlags = getPropagatingFlagsOfTypes(types, /*excludeKinds*/ TypeFlags.Nullable); + result.types = types; + result.aliasSymbol = aliasSymbol; // See comment in `getUnionTypeFromSortedList`. + result.aliasTypeArguments = aliasTypeArguments; + return result; + } + // We normalize combinations of intersection and union types based on the distributive property of the '&' // operator. Specifically, because X & (A | B) is equivalent to X & A | X & B, we can transform intersection // types with union type constituents into equivalent union types with intersection type constituents and @@ -9850,7 +9891,23 @@ namespace ts { function getIntersectionType(types: ReadonlyArray, aliasSymbol?: Symbol, aliasTypeArguments?: ReadonlyArray): Type { const typeSet: Type[] = []; const includes = addTypesToIntersection(typeSet, 0, types); - if (includes & TypeFlags.Never) { + // An intersection type is considered empty if it contains + // the type never, or + // more than one unit type or, + // an object type and a nullable type (null or undefined), or + // a string-like type and a type known to be non-string-like, or + // a number-like type and a type known to be non-number-like, or + // a symbol-like type and a type known to be non-symbol-like, or + // a void-like type and a type known to be non-void-like, or + // a non-primitive type and a type known to be primitive. + if (includes & TypeFlags.Never || + strictNullChecks && includes & TypeFlags.Nullable && includes & (TypeFlags.Object | TypeFlags.NonPrimitive | TypeFlags.IncludesEmptyObject) || + includes & TypeFlags.NonPrimitive && includes & (TypeFlags.DisjointDomains & ~TypeFlags.NonPrimitive) || + includes & TypeFlags.StringLike && includes & (TypeFlags.DisjointDomains & ~TypeFlags.StringLike) || + includes & TypeFlags.NumberLike && includes & (TypeFlags.DisjointDomains & ~TypeFlags.NumberLike) || + includes & TypeFlags.BigIntLike && includes & (TypeFlags.DisjointDomains & ~TypeFlags.BigIntLike) || + includes & TypeFlags.ESSymbolLike && includes & (TypeFlags.DisjointDomains & ~TypeFlags.ESSymbolLike) || + includes & TypeFlags.VoidLike && includes & (TypeFlags.DisjointDomains & ~TypeFlags.VoidLike)) { return neverType; } if (includes & TypeFlags.Any) { @@ -9874,31 +9931,37 @@ namespace ts { if (typeSet.length === 1) { return typeSet[0]; } - if (includes & TypeFlags.Union) { - if (intersectUnionsOfPrimitiveTypes(typeSet)) { - // When the intersection creates a reduced set (which might mean that *all* union types have - // disappeared), we restart the operation to get a new set of combined flags. Once we have - // reduced we'll never reduce again, so this occurs at most once. - return getIntersectionType(typeSet, aliasSymbol, aliasTypeArguments); - } - // 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. - const unionIndex = findIndex(typeSet, t => (t.flags & TypeFlags.Union) !== 0); - const unionType = typeSet[unionIndex]; - return getUnionType(map(unionType.types, t => getIntersectionType(replaceElement(typeSet, unionIndex, t))), - UnionReduction.Literal, aliasSymbol, aliasTypeArguments); - } const id = getTypeListId(typeSet); - let type = intersectionTypes.get(id); - if (!type) { - type = createType(TypeFlags.Intersection); - intersectionTypes.set(id, type); - type.objectFlags = getPropagatingFlagsOfTypes(typeSet, /*excludeKinds*/ TypeFlags.Nullable); - type.types = typeSet; - type.aliasSymbol = aliasSymbol; // See comment in `getUnionTypeFromSortedList`. - type.aliasTypeArguments = aliasTypeArguments; + let result = intersectionTypes.get(id); + if (!result) { + if (includes & TypeFlags.Union) { + if (intersectUnionsOfPrimitiveTypes(typeSet)) { + // When the intersection creates a reduced set (which might mean that *all* union types have + // disappeared), we restart the operation to get a new set of combined flags. Once we have + // reduced we'll never reduce again, so this occurs at most once. + result = getIntersectionType(typeSet, aliasSymbol, aliasTypeArguments); + } + 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))), + UnionReduction.Literal, aliasSymbol, aliasTypeArguments); + } + } + else { + result = createIntersectionType(typeSet, aliasSymbol, aliasTypeArguments); + } + intersectionTypes.set(id, result); } - return type; + return result; } function getTypeFromIntersectionTypeNode(node: IntersectionTypeNode): Type { @@ -10046,9 +10109,9 @@ namespace ts { return false; } - function getPropertyTypeForIndexType(originalObjectType: Type, objectType: Type, indexType: Type, accessNode: ElementAccessExpression | IndexedAccessTypeNode | PropertyName | BindingName | SyntheticExpression | undefined, accessFlags: AccessFlags) { + function getPropertyNameFromIndex(indexType: Type, accessNode: StringLiteral | Identifier | ObjectBindingPattern | ArrayBindingPattern | ComputedPropertyName | NumericLiteral | IndexedAccessTypeNode | ElementAccessExpression | SyntheticExpression | undefined) { const accessExpression = accessNode && accessNode.kind === SyntaxKind.ElementAccessExpression ? accessNode : undefined; - const propName = isTypeUsableAsPropertyName(indexType) ? + return isTypeUsableAsPropertyName(indexType) ? getPropertyNameFromType(indexType) : accessExpression && checkThatExpressionIsProperSymbolReference(accessExpression.argumentExpression, indexType, /*reportError*/ false) ? getPropertyNameForKnownSymbolName(idText((accessExpression.argumentExpression).name)) : @@ -10056,6 +10119,11 @@ namespace ts { // late bound names are handled in the first branch, so here we only need to handle normal names getPropertyNameForPropertyNameNode(accessNode) : undefined; + } + + 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 = getPropertyNameFromIndex(indexType, accessNode); if (propName !== undefined) { const prop = getPropertyOfType(objectType, propName); if (prop) { @@ -10085,6 +10153,7 @@ namespace ts { error(indexNode, Diagnostics.Property_0_does_not_exist_on_type_1, unescapeLeadingUnderscores(propName), typeToString(objectType)); } } + errorIfWritingToReadonlyIndex(getIndexInfoOfType(objectType, IndexKind.Number)); return mapType(objectType, t => getRestTypeOfTupleType(t) || undefinedType); } } @@ -10106,13 +10175,7 @@ namespace ts { error(indexNode, Diagnostics.Type_0_cannot_be_used_as_an_index_type, typeToString(indexType)); return indexInfo.type; } - if (indexInfo.isReadonly && accessExpression && (isAssignmentTarget(accessExpression) || isDeleteTarget(accessExpression))) { - if (accessExpression) { - error(accessExpression, Diagnostics.Index_signature_in_type_0_only_permits_reading, typeToString(objectType)); - return indexInfo.type; - } - return undefined; - } + errorIfWritingToReadonlyIndex(indexInfo); return indexInfo.type; } if (indexType.flags & TypeFlags.Never) { @@ -10125,7 +10188,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)); } @@ -10145,7 +10208,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)); } } } @@ -10172,6 +10257,12 @@ namespace ts { return indexType; } return undefined; + + function errorIfWritingToReadonlyIndex(indexInfo: IndexInfo | undefined): void { + if (indexInfo && indexInfo.isReadonly && accessExpression && (isAssignmentTarget(accessExpression) || isDeleteTarget(accessExpression))) { + error(accessExpression, Diagnostics.Index_signature_in_type_0_only_permits_reading, typeToString(objectType)); + } + } } function getIndexNodeForAccessExpression(accessNode: ElementAccessExpression | IndexedAccessTypeNode | PropertyName | BindingName | SyntheticExpression) { @@ -10192,8 +10283,14 @@ namespace ts { return maybeTypeOfKind(type, TypeFlags.InstantiableNonPrimitive | TypeFlags.Index); } + function isThisTypeParameter(type: Type): boolean { + return !!(type.flags & TypeFlags.TypeParameter && (type).isThisType); + } + function getSimplifiedType(type: Type, writing: boolean): Type { - return type.flags & TypeFlags.IndexedAccess ? getSimplifiedIndexedAccessType(type, writing) : type; + return type.flags & TypeFlags.IndexedAccess ? getSimplifiedIndexedAccessType(type, writing) : + type.flags & TypeFlags.Conditional ? getSimplifiedConditionalType(type, writing) : + type; } function distributeIndexOverObjectType(objectType: Type, indexType: Type, writing: boolean) { @@ -10256,6 +10353,38 @@ namespace ts { return type[cache] = type; } + function getSimplifiedConditionalType(type: ConditionalType, writing: boolean) { + const checkType = type.checkType; + const extendsType = type.extendsType; + const trueType = getTrueTypeFromConditionalType(type); + const falseType = getFalseTypeFromConditionalType(type); + // Simplifications for types of the form `T extends U ? T : never` and `T extends U ? never : T`. + if (falseType.flags & TypeFlags.Never && getActualTypeVariable(trueType) === getActualTypeVariable(checkType)) { + if (checkType.flags & TypeFlags.Any || isTypeAssignableTo(getRestrictiveInstantiation(checkType), getRestrictiveInstantiation(extendsType))) { // Always true + return getSimplifiedType(trueType, writing); + } + else if (isIntersectionEmpty(checkType, extendsType)) { // Always false + return neverType; + } + } + else if (trueType.flags & TypeFlags.Never && getActualTypeVariable(falseType) === getActualTypeVariable(checkType)) { + if (!(checkType.flags & TypeFlags.Any) && isTypeAssignableTo(getRestrictiveInstantiation(checkType), getRestrictiveInstantiation(extendsType))) { // Always true + return neverType; + } + else if (checkType.flags & TypeFlags.Any || isIntersectionEmpty(checkType, extendsType)) { // Always false + return getSimplifiedType(falseType, writing); + } + } + return type; + } + + /** + * Invokes union simplification logic to determine if an intersection is considered empty as a union constituent + */ + function isIntersectionEmpty(type1: Type, type2: Type) { + return !!(getUnionType([intersectTypes(type1, type2), neverType]).flags & TypeFlags.Never); + } + function substituteIndexedMappedType(objectType: MappedType, index: Type) { const mapper = createTypeMapper([getTypeParameterFromMappedType(objectType)], [index]); const templateMapper = combineTypeMappers(objectType.mapper, mapper); @@ -10300,7 +10429,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); } @@ -10318,7 +10447,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) { @@ -10362,50 +10491,12 @@ namespace ts { return type; } - /** - * Invokes union simplification logic to determine if an intersection is considered empty as a union constituent - */ - function isIntersectionEmpty(type1: Type, type2: Type) { - return !!(getUnionType([intersectTypes(type1, type2), neverType]).flags & TypeFlags.Never); - } - function getConditionalType(root: ConditionalRoot, mapper: TypeMapper | undefined): Type { const checkType = instantiateType(root.checkType, mapper); const extendsType = instantiateType(root.extendsType, mapper); if (checkType === wildcardType || extendsType === wildcardType) { return wildcardType; } - const trueType = instantiateType(root.trueType, mapper); - const falseType = instantiateType(root.falseType, mapper); - const instantiationId = `${root.isDistributive ? "d" : ""}${getTypeId(checkType)}>${getTypeId(extendsType)}?${getTypeId(trueType)}:${getTypeId(falseType)}`; - const result = conditionalTypes.get(instantiationId); - if (result) { - return result; - } - const newResult = getConditionalTypeWorker(root, mapper, checkType, extendsType, trueType, falseType); - conditionalTypes.set(instantiationId, newResult); - return newResult; - } - - function getConditionalTypeWorker(root: ConditionalRoot, mapper: TypeMapper | undefined, checkType: Type, extendsType: Type, trueType: Type, falseType: Type) { - // Simplifications for types of the form `T extends U ? T : never` and `T extends U ? never : T`. - if (falseType.flags & TypeFlags.Never && getActualTypeVariable(trueType) === getActualTypeVariable(checkType)) { - if (checkType.flags & TypeFlags.Any || isTypeAssignableTo(getRestrictiveInstantiation(checkType), getRestrictiveInstantiation(extendsType))) { // Always true - return trueType; - } - else if (isIntersectionEmpty(checkType, extendsType)) { // Always false - return neverType; - } - } - else if (trueType.flags & TypeFlags.Never && getActualTypeVariable(falseType) === getActualTypeVariable(checkType)) { - if (!(checkType.flags & TypeFlags.Any) && isTypeAssignableTo(getRestrictiveInstantiation(checkType), getRestrictiveInstantiation(extendsType))) { // Always true - return neverType; - } - else if (checkType.flags & TypeFlags.Any || isIntersectionEmpty(checkType, extendsType)) { // Always false - return falseType; - } - } - const checkTypeInstantiable = maybeTypeOfKind(checkType, TypeFlags.Instantiable | TypeFlags.GenericMappedType); let combinedMapper: TypeMapper | undefined; if (root.inferTypeParameters) { @@ -10423,18 +10514,18 @@ namespace ts { // We attempt to resolve the conditional type only when the check and extends types are non-generic if (!checkTypeInstantiable && !maybeTypeOfKind(inferredExtendsType, TypeFlags.Instantiable | TypeFlags.GenericMappedType)) { if (inferredExtendsType.flags & TypeFlags.AnyOrUnknown) { - return combinedMapper ? instantiateType(root.trueType, combinedMapper) : trueType; + return instantiateType(root.trueType, combinedMapper || mapper); } // Return union of trueType and falseType for 'any' since it matches anything if (checkType.flags & TypeFlags.Any) { - return getUnionType([combinedMapper ? instantiateType(root.trueType, combinedMapper) : trueType, falseType]); + return getUnionType([instantiateType(root.trueType, combinedMapper || mapper), instantiateType(root.falseType, mapper)]); } // Return falseType for a definitely false extends check. We check an instantiations of the two // types with type parameters mapped to the wildcard type, the most permissive instantiations // possible (the wildcard type is assignable to and from all types). If those are not related, // then no instantiations will be and we can just return the false branch type. if (!isTypeAssignableTo(getPermissiveInstantiation(checkType), getPermissiveInstantiation(inferredExtendsType))) { - return falseType; + return instantiateType(root.falseType, mapper); } // Return trueType for a definitely true extends check. We check instantiations of the two // types with type parameters mapped to their restrictive form, i.e. a form of the type parameter @@ -10442,14 +10533,10 @@ namespace ts { // type Foo = T extends { x: string } ? string : number // doesn't immediately resolve to 'string' instead of being deferred. if (isTypeAssignableTo(getRestrictiveInstantiation(checkType), getRestrictiveInstantiation(inferredExtendsType))) { - return combinedMapper ? instantiateType(root.trueType, combinedMapper) : trueType; + return instantiateType(root.trueType, combinedMapper || mapper); } } // Return a deferred type for a check that is neither definitely true nor definitely false - return getDeferredConditionalType(root, mapper, combinedMapper, checkType, extendsType, trueType, falseType); - } - - function getDeferredConditionalType(root: ConditionalRoot, mapper: TypeMapper | undefined, combinedMapper: TypeMapper | undefined, checkType: Type, extendsType: Type, trueType: Type, falseType: Type) { const erasedCheckType = getActualTypeVariable(checkType); const result = createType(TypeFlags.Conditional); result.root = root; @@ -10457,15 +10544,21 @@ namespace ts { result.extendsType = extendsType; result.mapper = mapper; result.combinedMapper = combinedMapper; - result.trueType = trueType; - result.falseType = falseType; result.aliasSymbol = root.aliasSymbol; result.aliasTypeArguments = instantiateTypes(root.aliasTypeArguments, mapper!); // TODO: GH#18217 return result; } + function getTrueTypeFromConditionalType(type: ConditionalType) { + return type.resolvedTrueType || (type.resolvedTrueType = instantiateType(type.root.trueType, type.mapper)); + } + + function getFalseTypeFromConditionalType(type: ConditionalType) { + return type.resolvedFalseType || (type.resolvedFalseType = instantiateType(type.root.falseType, type.mapper)); + } + function getInferredTrueTypeFromConditionalType(type: ConditionalType) { - return type.resolvedInferredTrueType || (type.resolvedInferredTrueType = instantiateType(type.root.trueType, type.combinedMapper || type.mapper)); + return type.resolvedInferredTrueType || (type.resolvedInferredTrueType = type.combinedMapper ? instantiateType(type.root.trueType, type.combinedMapper) : getTrueTypeFromConditionalType(type)); } function getInferTypeParameters(node: ConditionalTypeNode): TypeParameter[] | undefined { @@ -10480,21 +10573,6 @@ namespace ts { return result; } - function isPossiblyReferencedInConditionalType(tp: TypeParameter, node: Node) { - if (isTypeParameterPossiblyReferenced(tp, node)) { - return true; - } - while (node) { - if (node.kind === SyntaxKind.ConditionalType) { - if (isTypeParameterPossiblyReferenced(tp, (node).extendsType)) { - return true; - } - } - node = node.parent; - } - return false; - } - function getTypeFromConditionalTypeNode(node: ConditionalTypeNode): Type { const links = getNodeLinks(node); if (!links.resolvedType) { @@ -10502,7 +10580,7 @@ namespace ts { const aliasSymbol = getAliasSymbolForTypeNode(node); const aliasTypeArguments = getTypeArgumentsForAliasSymbol(aliasSymbol); const allOuterTypeParameters = getOuterTypeParameters(node, /*includeThisTypes*/ true); - const outerTypeParameters = aliasTypeArguments ? allOuterTypeParameters : filter(allOuterTypeParameters, tp => isPossiblyReferencedInConditionalType(tp, node)); + const outerTypeParameters = aliasTypeArguments ? allOuterTypeParameters : filter(allOuterTypeParameters, tp => isTypeParameterPossiblyReferenced(tp, node)); const root: ConditionalRoot = { node, checkType, @@ -10742,7 +10820,7 @@ namespace ts { emptyArray, getIndexInfoWithReadonly(stringIndexInfo, readonly), getIndexInfoWithReadonly(numberIndexInfo, readonly)); - spread.objectFlags |= ObjectFlags.ObjectLiteral | ObjectFlags.ContainsObjectLiteral | ObjectFlags.ContainsSpread | objectFlags; + spread.objectFlags |= ObjectFlags.ObjectLiteral | ObjectFlags.ContainsObjectOrArrayLiteral | ObjectFlags.ContainsSpread | objectFlags; return spread; } @@ -11196,9 +11274,12 @@ namespace ts { // type parameter, or if the node contains type queries, we consider the type parameter possibly referenced. if (tp.symbol && tp.symbol.declarations && tp.symbol.declarations.length === 1) { const container = tp.symbol.declarations[0].parent; - if (findAncestor(node, n => n.kind === SyntaxKind.Block ? "quit" : n === container)) { - return !!forEachChild(node, containsReference); + for (let n = node; n !== container; n = n.parent) { + if (!n || n.kind === SyntaxKind.Block || n.kind === SyntaxKind.ConditionalType && forEachChild((n).extendsType, containsReference)) { + return true; + } } + return !!forEachChild(node, containsReference); } return true; function containsReference(node: Node): boolean { @@ -11450,6 +11531,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); @@ -11483,6 +11565,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; @@ -12269,8 +12354,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); } /** @@ -12302,7 +12387,7 @@ namespace ts { let depth = 0; let expandingFlags = ExpandingFlags.None; let overflow = false; - let suppressNextError = false; + let overrideNextErrorInfo: DiagnosticMessageChain | undefined; Debug.assert(relation !== identityRelation || !errorNode, "no error reporting in identity checking"); @@ -12450,10 +12535,10 @@ namespace ts { if (target.flags & TypeFlags.Substitution) { target = (target).typeVariable; } - if (source.flags & TypeFlags.IndexedAccess) { + if (source.flags & TypeFlags.Simplifiable) { source = getSimplifiedType(source, /*writing*/ false); } - if (target.flags & TypeFlags.IndexedAccess) { + if (target.flags & TypeFlags.Simplifiable) { target = getSimplifiedType(target, /*writing*/ true); } @@ -12532,8 +12617,8 @@ namespace ts { result = typeRelatedToSomeType(getRegularTypeOfObjectLiteral(source), target, reportErrors && !(source.flags & TypeFlags.Primitive) && !(target.flags & TypeFlags.Primitive)); if (result && isPerformingExcessPropertyChecks) { // Validate against excess props using the original `source` - const discriminantType = target.flags & TypeFlags.Union ? findMatchingDiscriminantType(source, target as UnionType) : undefined; - if (!propertiesRelatedTo(source, discriminantType || target, reportErrors, /*excludedProperties*/ undefined)) { + const discriminantType = findMatchingDiscriminantType(source, target as UnionType) || filterPrimitivesIfContainsNonPrimitive(target as UnionType); + if (!propertiesRelatedTo(source, discriminantType, reportErrors, /*excludedProperties*/ undefined)) { return Ternary.False; } } @@ -12589,10 +12674,14 @@ namespace ts { } if (!result && reportErrors) { - const maybeSuppress = suppressNextError; - suppressNextError = false; + let maybeSuppress = overrideNextErrorInfo; + overrideNextErrorInfo = undefined; if (source.flags & TypeFlags.Object && target.flags & TypeFlags.Object) { + const currentError = errorInfo; tryElaborateArrayLikeErrors(source, target, reportErrors); + if (errorInfo !== currentError) { + maybeSuppress = errorInfo; + } } if (source.flags & TypeFlags.Object && target.flags & TypeFlags.Primitive) { tryElaborateErrorsForPrimitivesAndObjects(source, target); @@ -12800,6 +12889,16 @@ namespace ts { return bestMatch; } + function filterPrimitivesIfContainsNonPrimitive(type: UnionType) { + if (maybeTypeOfKind(type, TypeFlags.NonPrimitive)) { + const result = filterType(type, t => !(t.flags & TypeFlags.Primitive)); + if (!(result.flags & TypeFlags.Never)) { + return result; + } + } + return type; + } + // Keep this up-to-date with the same logic within `getApparentTypeOfContextualType`, since they should behave similarly function findMatchingDiscriminantType(source: Type, target: Type) { if (target.flags & TypeFlags.Union && source.flags & (TypeFlags.Intersection | TypeFlags.Object)) { @@ -12912,6 +13011,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 @@ -12929,6 +13040,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; } } @@ -12997,8 +13118,8 @@ namespace ts { if ((source).root.isDistributive === (target).root.isDistributive) { if (result = isRelatedTo((source).checkType, (target).checkType, /*reportErrors*/ false)) { if (result &= isRelatedTo((source).extendsType, (target).extendsType, /*reportErrors*/ false)) { - if (result &= isRelatedTo((source).trueType, (target).trueType, /*reportErrors*/ false)) { - if (result &= isRelatedTo((source).falseType, (target).falseType, /*reportErrors*/ false)) { + if (result &= isRelatedTo(getTrueTypeFromConditionalType(source), getTrueTypeFromConditionalType(target), /*reportErrors*/ false)) { + if (result &= isRelatedTo(getFalseTypeFromConditionalType(source), getFalseTypeFromConditionalType(target), /*reportErrors*/ false)) { return result; } } @@ -13051,8 +13172,7 @@ namespace ts { } // A type S is assignable to keyof T if S is assignable to keyof C, where C is the // simplified form of T or, if T doesn't simplify, the constraint of T. - const simplified = getSimplifiedType((target).type, /*writing*/ false); - const constraint = simplified !== (target).type ? simplified : getConstraintOfType((target).type); + const constraint = getSimplifiedTypeOrConstraint((target).type); if (constraint) { // We require Ternary.True here such that circular constraints don't cause // false positives. For example, given 'T extends { [K in keyof T]: string }', @@ -13092,14 +13212,14 @@ namespace ts { if (!isGenericMappedType(source)) { const targetConstraint = getConstraintTypeFromMappedType(target); const sourceKeys = getIndexType(source, /*stringsOnly*/ undefined, /*noIndexSignatures*/ true); - const hasOptionalUnionKeys = modifiers & MappedTypeModifiers.IncludeOptional && targetConstraint.flags & TypeFlags.Union; - const filteredByApplicability = hasOptionalUnionKeys ? filterType(targetConstraint, t => !!isRelatedTo(t, sourceKeys)) : undefined; + const includeOptional = modifiers & MappedTypeModifiers.IncludeOptional; + const filteredByApplicability = includeOptional ? intersectTypes(targetConstraint, sourceKeys) : undefined; // A source type T is related to a target type { [P in Q]: X } if Q is related to keyof T and T[Q] is related to X. // A source type T is related to a target type { [P in Q]?: X } if some constituent Q' of Q is related to keyof T and T[Q'] is related to X. - if (hasOptionalUnionKeys + if (includeOptional ? !(filteredByApplicability!.flags & TypeFlags.Never) : isRelatedTo(targetConstraint, sourceKeys)) { - const indexingType = hasOptionalUnionKeys ? filteredByApplicability! : getTypeParameterFromMappedType(target); + const indexingType = filteredByApplicability || getTypeParameterFromMappedType(target); const indexedAccessType = getIndexedAccessType(source, indexingType); const templateType = getTemplateTypeFromMappedType(target); if (result = isRelatedTo(indexedAccessType, templateType, reportErrors)) { @@ -13157,8 +13277,8 @@ namespace ts { // and Y1 is related to Y2. if (isTypeIdenticalTo((source).extendsType, (target).extendsType) && (isRelatedTo((source).checkType, (target).checkType) || isRelatedTo((target).checkType, (source).checkType))) { - if (result = isRelatedTo((source).trueType, (target).trueType, reportErrors)) { - result &= isRelatedTo((source).falseType, (target).falseType, reportErrors); + if (result = isRelatedTo(getTrueTypeFromConditionalType(source), getTrueTypeFromConditionalType(target), reportErrors)) { + result &= isRelatedTo(getFalseTypeFromConditionalType(source), getFalseTypeFromConditionalType(target), reportErrors); } if (result) { errorInfo = saveErrorInfo; @@ -13213,7 +13333,14 @@ namespace ts { } } else if (isReadonlyArrayType(target) ? isArrayType(source) || isTupleType(source) : isArrayType(target) && isTupleType(source) && !source.target.readonly) { - return isRelatedTo(getIndexTypeOfType(source, IndexKind.Number) || anyType, getIndexTypeOfType(target, IndexKind.Number) || anyType, reportErrors); + if (relation !== identityRelation) { + return isRelatedTo(getIndexTypeOfType(source, IndexKind.Number) || anyType, getIndexTypeOfType(target, IndexKind.Number) || anyType, reportErrors); + } + else { + // By flags alone, we know that the `target` is a readonly array while the source is a normal array or tuple + // or `target` is an array and source is a tuple - in both cases the types cannot be identical, by construction + return Ternary.False; + } } // Consider a fresh empty object literal type "closed" under the subtype relationship - this way `{} <- {[idx: string]: any} <- fresh({})` // and not `{} <- fresh({}) <- {[idx: string]: any}` @@ -13448,6 +13575,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); @@ -13490,7 +13660,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)); @@ -13524,9 +13694,10 @@ namespace ts { if (unmatchedProperty) { if (reportErrors) { const props = arrayFrom(getUnmatchedProperties(source, target, requireOptionalProperties, /*matchDiscriminantProperties*/ false)); + let shouldSkipElaboration = false; if (!headMessage || (headMessage.code !== Diagnostics.Class_0_incorrectly_implements_interface_1.code && headMessage.code !== Diagnostics.Class_0_incorrectly_implements_class_1_Did_you_mean_to_extend_1_and_inherit_its_members_as_a_subclass.code)) { - suppressNextError = true; // Retain top-level error for interface implementing issues, otherwise omit it + shouldSkipElaboration = true; // Retain top-level error for interface implementing issues, otherwise omit it } if (props.length === 1) { const propName = symbolToString(unmatchedProperty); @@ -13534,6 +13705,9 @@ namespace ts { if (length(unmatchedProperty.declarations)) { associateRelatedInfo(createDiagnosticForNode(unmatchedProperty.declarations[0], Diagnostics._0_is_declared_here, propName)); } + if (shouldSkipElaboration) { + overrideNextErrorInfo = errorInfo; + } } else if (tryElaborateArrayLikeErrors(source, target, /*reportErrors*/ false)) { if (props.length > 5) { // arbitrary cutoff for too-long list form @@ -13542,7 +13716,11 @@ namespace ts { else { reportError(Diagnostics.Type_0_is_missing_the_following_properties_from_type_1_Colon_2, typeToString(source), typeToString(target), map(props, p => symbolToString(p)).join(", ")); } + if (shouldSkipElaboration) { + overrideNextErrorInfo = errorInfo; + } } + // ELSE: No array like or unmatched property error - just issue top level error (errorInfo = undefined) } return Ternary.False; } @@ -13594,9 +13772,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; @@ -13742,7 +13917,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 @@ -13954,12 +14129,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); } @@ -14168,20 +14337,25 @@ namespace ts { if (!(isMatchingSignature(source, target, partialMatch))) { return Ternary.False; } - // Check that the two signatures have the same number of type parameters. We might consider - // also checking that any type parameter constraints match, but that would require instantiating - // the constraints with a common set of type arguments to get relatable entities in places where - // type parameters occur in the constraints. The complexity of doing that doesn't seem worthwhile, - // particularly as we're comparing erased versions of the signatures below. + // Check that the two signatures have the same number of type parameters. if (length(source.typeParameters) !== length(target.typeParameters)) { return Ternary.False; } - // Spec 1.0 Section 3.8.3 & 3.8.4: - // M and N (the signatures) are instantiated using type Any as the type argument for all type parameters declared by M and N - source = getErasedSignature(source); - target = getErasedSignature(target); + // Check that type parameter constraints and defaults match. If they do, instantiate the source + // signature with the type parameters of the target signature and continue the comparison. + if (target.typeParameters) { + const mapper = createTypeMapper(source.typeParameters!, target.typeParameters); + for (let i = 0; i < target.typeParameters.length; i++) { + const s = source.typeParameters![i]; + const t = target.typeParameters[i]; + if (!(s === t || compareTypes(instantiateType(getConstraintFromTypeParameter(s), mapper) || unknownType, getConstraintFromTypeParameter(t) || unknownType) && + compareTypes(instantiateType(getDefaultFromTypeParameter(s), mapper) || unknownType, getDefaultFromTypeParameter(t) || unknownType))) { + return Ternary.False; + } + } + source = instantiateSignature(source, mapper, /*eraseTypeParameters*/ true); + } let result = Ternary.True; - if (!ignoreThisTypes) { const sourceThisType = getThisTypeOfSignature(source); if (sourceThisType) { @@ -14195,7 +14369,6 @@ namespace ts { } } } - const targetLen = getParameterCount(target); for (let i = 0; i < targetLen; i++) { const s = getTypeAtPosition(source, i); @@ -14295,7 +14468,7 @@ namespace ts { if (propType) { return propType; } - if (everyType(type, isTupleType) && !everyType(type, t => !(t).target.hasRestElement)) { + if (everyType(type, isTupleType)) { return mapType(type, t => getRestTypeOfTupleType(t) || undefinedType); } return undefined; @@ -14480,12 +14653,12 @@ 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); } function createSymbolWithType(source: Symbol, type: Type | undefined) { - const symbol = createSymbol(source.flags, source.escapedName); + const symbol = createSymbol(source.flags, source.escapedName, getCheckFlags(source) & CheckFlags.Readonly); symbol.declarations = source.declarations; symbol.parent = source.parent; symbol.type = type; @@ -14624,26 +14797,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; } @@ -14809,13 +14990,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, @@ -14931,10 +15105,19 @@ namespace ts { return type; } + // We consider a type to be partially inferable if it isn't marked non-inferable or if it is + // an object literal type with at least one property of an inferable type. For example, an object + // literal { a: 123, b: x => true } is marked non-inferable because it contains a context sensitive + // arrow function, but is considered partially inferable because property 'a' has an inferable type. + function isPartiallyInferableType(type: Type): boolean { + return !(getObjectFlags(type) & ObjectFlags.NonInferrableType) || + isObjectLiteralType(type) && some(getPropertiesOfType(type), prop => isPartiallyInferableType(getTypeOfSymbol(prop))); + } + function createReverseMappedType(source: Type, target: MappedType, constraint: IndexType) { - // If any property contains context sensitive functions that have been skipped, the source type - // is incomplete and we can't infer a meaningful input type. - if (getObjectFlags(source) & ObjectFlags.NonInferrableType || getPropertiesOfType(source).length === 0 && !getIndexInfoOfType(source, IndexKind.String)) { + // We consider a source type reverse mappable if it has a string index signature or if + // it has one or more properties and is of a partially inferable type. + if (!(getIndexInfoOfType(source, IndexKind.String) || getPropertiesOfType(source).length !== 0 && isPartiallyInferableType(source))) { return undefined; } // For arrays and tuples we infer new arrays and tuples where the reverse mapping has been @@ -15038,11 +15221,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) || @@ -15148,18 +15327,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; @@ -15182,12 +15350,12 @@ namespace ts { else if (source.flags & TypeFlags.Conditional && target.flags & TypeFlags.Conditional) { inferFromTypes((source).checkType, (target).checkType); inferFromTypes((source).extendsType, (target).extendsType); - inferFromTypes((source).trueType, (target).trueType); - inferFromTypes((source).falseType, (target).falseType); + inferFromTypes(getTrueTypeFromConditionalType(source), getTrueTypeFromConditionalType(target)); + inferFromTypes(getFalseTypeFromConditionalType(source), getFalseTypeFromConditionalType(target)); } else if (target.flags & TypeFlags.Conditional && !contravariant) { - inferFromTypes(source, (target).trueType); - inferFromTypes(source, (target).falseType); + inferFromTypes(source, getTrueTypeFromConditionalType(target)); + inferFromTypes(source, getFalseTypeFromConditionalType(target)); } else if (target.flags & TypeFlags.UnionOrIntersection) { // We infer from types that are not naked type variables first so that inferences we @@ -15279,6 +15447,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; @@ -15319,7 +15499,11 @@ namespace ts { const inferredType = inferTypeForHomomorphicMappedType(source, target, constraintType); if (inferredType) { const savePriority = priority; - priority |= InferencePriority.HomomorphicMappedType; + // We assign a lower priority to inferences made from types containing non-inferrable + // types because we may only have a partial result (i.e. we may have failed to make + // reverse inferences for some properties). + priority |= getObjectFlags(source) & ObjectFlags.NonInferrableType ? + InferencePriority.PartialHomomorphicMappedType : InferencePriority.HomomorphicMappedType; inferFromTypes(inferredType, inference.typeParameter); priority = savePriority; } @@ -15488,12 +15672,16 @@ namespace ts { return !!(getObjectFlags(type) & ObjectFlags.ObjectLiteral); } - function widenObjectLiteralCandidates(candidates: Type[]): Type[] { + function isObjectOrArrayLiteralType(type: Type) { + return !!(getObjectFlags(type) & (ObjectFlags.ObjectLiteral | ObjectFlags.ArrayLiteral)); + } + + function unionObjectAndArrayLiteralCandidates(candidates: Type[]): Type[] { if (candidates.length > 1) { - const objectLiterals = filter(candidates, isObjectLiteralType); + const objectLiterals = filter(candidates, isObjectOrArrayLiteralType); if (objectLiterals.length) { - const objectLiteralsType = getWidenedType(getUnionType(objectLiterals, UnionReduction.Subtype)); - return concatenate(filter(candidates, t => !isObjectLiteralType(t)), [objectLiteralsType]); + const literalsType = getUnionType(objectLiterals, UnionReduction.Subtype); + return concatenate(filter(candidates, t => !isObjectOrArrayLiteralType(t)), [literalsType]); } } return candidates; @@ -15504,8 +15692,8 @@ namespace ts { } function getCovariantInference(inference: InferenceInfo, signature: Signature) { - // Extract all object literal types and replace them with a single widened and normalized type. - const candidates = widenObjectLiteralCandidates(inference.candidates!); + // Extract all object and array literal types and replace them with a single widened and normalized type. + const candidates = unionObjectAndArrayLiteralCandidates(inference.candidates!); // We widen inferred literal types if // all inferences were made to top-level occurrences of the type parameter, and // the type parameter has no constraint or its constraint includes no primitive or literal types, and @@ -15666,21 +15854,21 @@ namespace ts { // The result is undefined if the reference isn't a dotted name. We prefix nodes // occurring in an apparent type position with '@' because the control flow type // of such nodes may be based on the apparent type instead of the declared type. - function getFlowCacheKey(node: Node): string | undefined { + function getFlowCacheKey(node: Node, declaredType: Type, initialType: Type, flowContainer: Node | undefined): string | undefined { switch (node.kind) { case SyntaxKind.Identifier: const symbol = getResolvedSymbol(node); - return symbol !== unknownSymbol ? (isConstraintPosition(node) ? "@" : "") + getSymbolId(symbol) : undefined; + return symbol !== unknownSymbol ? `${flowContainer ? getNodeId(flowContainer) : "-1"}|${getTypeId(declaredType)}|${getTypeId(initialType)}|${isConstraintPosition(node) ? "@" : ""}${getSymbolId(symbol)}` : undefined; case SyntaxKind.ThisKeyword: return "0"; case SyntaxKind.NonNullExpression: case SyntaxKind.ParenthesizedExpression: - return getFlowCacheKey((node).expression); + return getFlowCacheKey((node).expression, declaredType, initialType, flowContainer); case SyntaxKind.PropertyAccessExpression: case SyntaxKind.ElementAccessExpression: const propName = getAccessedPropertyName(node); if (propName !== undefined) { - const key = getFlowCacheKey((node).expression); + const key = getFlowCacheKey((node).expression, declaredType, initialType, flowContainer); return key && key + "." + propName; } } @@ -16345,6 +16533,7 @@ namespace ts { function getFlowTypeOfReference(reference: Node, declaredType: Type, initialType = declaredType, flowContainer?: Node, couldBeUninitialized?: boolean) { let key: string | undefined; + let keySet = false; let flowDepth = 0; if (flowAnalysisDisabled) { return errorType; @@ -16365,6 +16554,14 @@ namespace ts { } return resultType; + function getOrSetCacheKey() { + if (keySet) { + return key; + } + keySet = true; + return key = getFlowCacheKey(reference, declaredType, initialType, flowContainer); + } + function getTypeAtFlowNode(flow: FlowNode): FlowType { if (flowDepth === 2000) { // We have made 2000 recursive invocations. To avoid overflowing the call stack we report an error @@ -16376,6 +16573,15 @@ namespace ts { flowDepth++; while (true) { const flags = flow.flags; + if (flags & FlowFlags.Cached) { + const key = getOrSetCacheKey(); + if (key) { + const id = getFlowNodeId(flow); + if (flowAssignmentKeys[id] === key) { + return flowAssignmentTypes[id]; + } + } + } if (flags & FlowFlags.Shared) { // We cache results of flow type resolution for shared nodes that were previously visited in // the same getFlowTypeOfReference invocation. A node is considered shared when it is the @@ -16406,6 +16612,15 @@ namespace ts { flow = (flow).antecedent; continue; } + else if (flowLoopCount === flowLoopStart) { // Only cache assignments when not within loop analysis + const key = getOrSetCacheKey(); + if (key && !isIncomplete(type)) { + flow.flags |= FlowFlags.Cached; + const id = getFlowNodeId(flow); + flowAssignmentKeys[id] = key; + flowAssignmentTypes[id] = type; + } + } } else if (flags & FlowFlags.Condition) { type = getTypeAtFlowCondition(flow); @@ -16618,12 +16833,10 @@ namespace ts { // this flow loop junction, return the cached type. const id = getFlowNodeId(flow); const cache = flowLoopCaches[id] || (flowLoopCaches[id] = createMap()); + const key = getOrSetCacheKey(); if (!key) { - key = getFlowCacheKey(reference); // No cache key is generated when binding patterns are in unnarrowable situations - if (!key) { - return declaredType; - } + return declaredType; } const cached = cache.get(key); if (cached) { @@ -16735,7 +16948,7 @@ namespace ts { } function narrowByInKeyword(type: Type, literal: LiteralExpression, assumeTrue: boolean) { - if ((type.flags & (TypeFlags.Union | TypeFlags.Object)) || (type.flags & TypeFlags.TypeParameter && (type as TypeParameter).isThisType)) { + if (type.flags & (TypeFlags.Union | TypeFlags.Object) || isThisTypeParameter(type)) { const propName = escapeLeadingUnderscores(literal.text); return filterType(type, t => isTypePresencePossible(t, propName, assumeTrue)); } @@ -16745,7 +16958,7 @@ namespace ts { function narrowTypeByBinaryExpression(type: Type, expr: BinaryExpression, assumeTrue: boolean): Type { switch (expr.operatorToken.kind) { case SyntaxKind.EqualsToken: - return narrowTypeByTruthiness(type, expr.left, assumeTrue); + return narrowTypeByTruthiness(narrowType(type, expr.right, assumeTrue), expr.left, assumeTrue); case SyntaxKind.EqualsEqualsToken: case SyntaxKind.ExclamationEqualsToken: case SyntaxKind.EqualsEqualsEqualsToken: @@ -18089,7 +18302,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; @@ -18110,8 +18323,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) : @@ -18126,7 +18352,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 @@ -18283,11 +18509,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: @@ -18894,8 +19122,8 @@ namespace ts { } function checkSpreadExpression(node: SpreadElement, checkMode?: CheckMode): Type { - if (languageVersion < ScriptTarget.ES2015 && compilerOptions.downlevelIteration) { - checkExternalEmitHelpers(node, ExternalEmitHelpers.SpreadIncludes); + if (languageVersion < ScriptTarget.ES2015) { + checkExternalEmitHelpers(node, compilerOptions.downlevelIteration ? ExternalEmitHelpers.SpreadIncludes : ExternalEmitHelpers.SpreadArrays); } const arrayOrIterableType = checkExpression(node.expression, checkMode); @@ -18951,22 +19179,34 @@ namespace ts { const minLength = elementCount - (hasRestElement ? 1 : 0); // If array literal is actually a destructuring pattern, mark it as an implied type. We do this such // that we get the same behavior for "var [x, y] = []" and "[x, y] = []". - let tupleResult: Type | undefined; + let tupleResult; if (inDestructuringPattern && minLength > 0) { const type = cloneTypeReference(createTupleType(elementTypes, minLength, hasRestElement)); type.pattern = node; return type; } else if (tupleResult = getArrayLiteralTupleTypeIfApplicable(elementTypes, contextualType, hasRestElement, elementCount, inConstContext)) { - return tupleResult; + return createArrayLiteralType(tupleResult); } else if (forceTuple) { - return createTupleType(elementTypes, minLength, hasRestElement); + return createArrayLiteralType(createTupleType(elementTypes, minLength, hasRestElement)); } } - return createArrayType(elementTypes.length ? + return createArrayLiteralType(createArrayType(elementTypes.length ? getUnionType(elementTypes, UnionReduction.Subtype) : - strictNullChecks ? implicitNeverType : undefinedWideningType, inConstContext); + strictNullChecks ? implicitNeverType : undefinedWideningType, inConstContext)); + } + + function createArrayLiteralType(type: ObjectType) { + if (!(getObjectFlags(type) & ObjectFlags.Reference)) { + return type; + } + let literalType = (type).literalType; + if (!literalType) { + literalType = (type).literalType = cloneTypeReference(type); + literalType.objectFlags |= ObjectFlags.ArrayLiteral | ObjectFlags.ContainsObjectOrArrayLiteral; + } + return literalType; } function getArrayLiteralTupleTypeIfApplicable(elementTypes: Type[], contextualType: Type | undefined, hasRestElement: boolean, elementCount = elementTypes.length, readonly = false) { @@ -19251,7 +19491,7 @@ namespace ts { const stringIndexInfo = hasComputedStringProperty ? getObjectLiteralIndexInfo(node, offset, propertiesArray, IndexKind.String) : undefined; const numberIndexInfo = hasComputedNumberProperty ? getObjectLiteralIndexInfo(node, offset, propertiesArray, IndexKind.Number) : undefined; const result = createAnonymousType(node.symbol, propertiesTable, emptyArray, emptyArray, stringIndexInfo, numberIndexInfo); - result.objectFlags |= objectFlags | ObjectFlags.ObjectLiteral | ObjectFlags.ContainsObjectLiteral; + result.objectFlags |= objectFlags | ObjectFlags.ObjectLiteral | ObjectFlags.ContainsObjectOrArrayLiteral; if (isJSObjectLiteral) { result.objectFlags |= ObjectFlags.JSLiteral; } @@ -19447,7 +19687,7 @@ namespace ts { function createJsxAttributesType() { objectFlags |= freshObjectLiteralFlag; const result = createAnonymousType(attributes.symbol, attributesTable, emptyArray, emptyArray, /*stringIndexInfo*/ undefined, /*numberIndexInfo*/ undefined); - result.objectFlags |= objectFlags | ObjectFlags.ObjectLiteral | ObjectFlags.ContainsObjectLiteral; + result.objectFlags |= objectFlags | ObjectFlags.ObjectLiteral | ObjectFlags.ContainsObjectOrArrayLiteral; return result; } } @@ -19809,6 +20049,7 @@ namespace ts { } function checkJsxExpression(node: JsxExpression, checkMode?: CheckMode) { + checkGrammarJsxExpression(node); if (node.expression) { const type = checkExpression(node.expression, checkMode); if (node.dotDotDotToken && type !== anyType && !isArrayType(type)) { @@ -20023,24 +20264,31 @@ namespace ts { return checkPropertyAccessExpressionOrQualifiedName(node, node.left, node.right); } + function isMethodAccessForCall(node: Node) { + while (node.parent.kind === SyntaxKind.ParenthesizedExpression) { + node = node.parent; + } + return isCallOrNewExpression(node.parent) && node.parent.expression === node; + } + function checkPropertyAccessExpressionOrQualifiedName(node: PropertyAccessExpression | QualifiedName, left: Expression | QualifiedName, right: Identifier) { let propType: Type; const leftType = checkNonNullExpression(left); const parentSymbol = getNodeLinks(left).resolvedSymbol; - const apparentType = getApparentType(getWidenedType(leftType)); + const assignmentKind = getAssignmentTargetKind(node); + const apparentType = getApparentType(assignmentKind !== AssignmentKind.None || isMethodAccessForCall(node) ? getWidenedType(leftType) : leftType); if (isTypeAny(apparentType) || apparentType === silentNeverType) { if (isIdentifier(left) && parentSymbol) { markAliasReferenced(parentSymbol, node); } return apparentType; } - const assignmentKind = getAssignmentTargetKind(node); const prop = getPropertyOfType(apparentType, right.escapedText); if (isIdentifier(left) && parentSymbol && !(prop && isConstEnumOrConstEnumOnlyModule(prop))) { 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; @@ -20055,7 +20303,7 @@ namespace ts { return anyType; } if (right.escapedText && !checkAndReportErrorForExtendingInterface(node)) { - reportNonexistentProperty(right, leftType.flags & TypeFlags.TypeParameter && (leftType as TypeParameter).isThisType ? apparentType : leftType); + reportNonexistentProperty(right, isThisTypeParameter(leftType) ? apparentType : leftType); } return errorType; } @@ -20358,25 +20606,8 @@ namespace ts { } function isValidPropertyAccessForCompletions(node: PropertyAccessExpression | ImportTypeNode | QualifiedName, type: Type, property: Symbol): boolean { - return isValidPropertyAccessWithType(node, node.kind === SyntaxKind.PropertyAccessExpression && node.expression.kind === SyntaxKind.SuperKeyword, property.escapedName, type) - && (!(property.flags & SymbolFlags.Method) || isValidMethodAccess(property, type)); - } - function isValidMethodAccess(method: Symbol, actualThisType: Type): boolean { - const propType = getTypeOfPropertyOfType(actualThisType, method.escapedName)!; - const signatures = getSignaturesOfType(getNonNullableType(propType), SignatureKind.Call); - Debug.assert(signatures.length !== 0); - return signatures.some(sig => { - const signatureThisType = getThisTypeOfSignature(sig); - return !signatureThisType || isTypeAssignableTo(actualThisType, getInstantiatedSignatureThisType(sig, signatureThisType, actualThisType)); - }); - } - function getInstantiatedSignatureThisType(sig: Signature, signatureThisType: Type, actualThisType: Type): Type { - if (!sig.typeParameters) { - return signatureThisType; - } - const context = createInferenceContext(sig.typeParameters, sig, InferenceFlags.None); - inferTypes(context.inferences, actualThisType, signatureThisType); - return instantiateType(signatureThisType, createSignatureTypeMapper(sig, getInferredTypes(context))); + return isValidPropertyAccessWithType(node, node.kind === SyntaxKind.PropertyAccessExpression && node.expression.kind === SyntaxKind.SuperKeyword, property.escapedName, type); + // Previously we validated the 'this' type of methods but this adversely affected performance. See #31377 for more context. } function isValidPropertyAccessWithType( @@ -20445,7 +20676,8 @@ namespace ts { } function checkIndexedAccess(node: ElementAccessExpression): Type { - const objectType = checkNonNullExpression(node.expression); + const exprType = checkNonNullExpression(node.expression); + const objectType = getAssignmentTargetKind(node) !== AssignmentKind.None || isMethodAccessForCall(node) ? getWidenedType(exprType) : exprType; const indexExpression = node.argumentExpression; if (!indexExpression) { @@ -20476,7 +20708,7 @@ namespace ts { const effectiveIndexType = isForInVariableForNumericPropertyNames(indexExpression) ? numberType : indexType; const accessFlags = isAssignmentTarget(node) ? - AccessFlags.Writing | (isGenericObjectType(objectType) ? AccessFlags.NoIndexSignatures : 0) : + AccessFlags.Writing | (isGenericObjectType(objectType) && !isThisTypeParameter(objectType) ? AccessFlags.NoIndexSignatures : 0) : AccessFlags.None; const indexedAccessType = getIndexedAccessTypeOrUndefined(objectType, effectiveIndexType, node, accessFlags) || errorType; return checkIndexedAccessIndexType(indexedAccessType, node); @@ -20770,7 +21002,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 @@ -20787,8 +21020,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; } } @@ -21076,8 +21314,34 @@ namespace ts { return Debug.fail(); } } + function getDiagnosticSpanForCallNode(node: CallExpression, doNotIncludeArguments?: boolean) { + let start: number; + let length: number; + const sourceFile = getSourceFileOfNode(node); - function getArgumentArityError(node: Node, signatures: ReadonlyArray, args: ReadonlyArray) { + if (isPropertyAccessExpression(node.expression)) { + const nameSpan = getErrorSpanForNode(sourceFile, node.expression.name); + start = nameSpan.start; + length = doNotIncludeArguments ? nameSpan.length : node.end - start; + } + else { + const expressionSpan = getErrorSpanForNode(sourceFile, node.expression); + start = expressionSpan.start; + length = doNotIncludeArguments ? expressionSpan.length : node.end - start; + } + return { start, length, sourceFile }; + } + function getDiagnosticForCallNode(node: CallLikeExpression, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number, arg3?: string | number): DiagnosticWithLocation { + if (isCallExpression(node)) { + const { sourceFile, start, length } = getDiagnosticSpanForCallNode(node); + return createFileDiagnostic(sourceFile, start, length, message, arg0, arg1, arg2, arg3); + } + else { + return createDiagnosticForNode(node, message, arg0, arg1, arg2, arg3); + } + } + + function getArgumentArityError(node: CallLikeExpression, signatures: ReadonlyArray, args: ReadonlyArray) { let min = Number.POSITIVE_INFINITY; let max = Number.NEGATIVE_INFINITY; let belowArgCount = Number.NEGATIVE_INFINITY; @@ -21124,11 +21388,11 @@ namespace ts { } } if (min < argCount && argCount < max) { - return createDiagnosticForNode(node, Diagnostics.No_overload_expects_0_arguments_but_overloads_do_exist_that_expect_either_1_or_2_arguments, argCount, belowArgCount, aboveArgCount); + return getDiagnosticForCallNode(node, Diagnostics.No_overload_expects_0_arguments_but_overloads_do_exist_that_expect_either_1_or_2_arguments, argCount, belowArgCount, aboveArgCount); } if (!hasSpreadArgument && argCount < min) { - const diagnostic = createDiagnosticForNode(node, error, paramRange, argCount); + const diagnostic = getDiagnosticForCallNode(node, error, paramRange, argCount); return related ? addRelatedInfo(diagnostic, related) : diagnostic; } @@ -21203,7 +21467,7 @@ namespace ts { reorderCandidates(signatures, candidates); if (!candidates.length) { if (reportErrors) { - diagnostics.add(createDiagnosticForNode(node, Diagnostics.Call_target_does_not_contain_any_signatures)); + diagnostics.add(getDiagnosticForCallNode(node, Diagnostics.Call_target_does_not_contain_any_signatures)); } return resolveErrorCall(node); } @@ -21281,6 +21545,7 @@ namespace ts { // If candidate is undefined, it means that no candidates had a suitable arity. In that case, // skip the checkApplicableSignature check. if (reportErrors) { + if (candidateForArgumentError) { checkApplicableSignature(node, args, candidateForArgumentError, assignableRelation, CheckMode.Normal, /*reportErrors*/ true); } @@ -21299,7 +21564,7 @@ namespace ts { diagnostics.add(getArgumentArityError(node, signaturesWithCorrectTypeArgumentArity, args)); } else if (fallbackError) { - diagnostics.add(createDiagnosticForNode(node, fallbackError)); + diagnostics.add(getDiagnosticForCallNode(node, fallbackError)); } } } @@ -21588,7 +21853,7 @@ namespace ts { relatedInformation = createDiagnosticForNode(node.expression, Diagnostics.It_is_highly_likely_that_you_are_missing_a_semicolon); } } - invocationError(node, apparentType, SignatureKind.Call, relatedInformation); + invocationError(node.expression, apparentType, SignatureKind.Call, relatedInformation); } return resolveErrorCall(node); } @@ -21705,7 +21970,7 @@ namespace ts { return signature; } - invocationError(node, expressionType, SignatureKind.Construct); + invocationError(node.expression, expressionType, SignatureKind.Construct); return resolveErrorCall(node); } @@ -21778,11 +22043,88 @@ namespace ts { return true; } - function invocationError(node: Node, apparentType: Type, kind: SignatureKind, relatedInformation?: DiagnosticRelatedInformation) { - const diagnostic = error(node, (kind === SignatureKind.Call ? - Diagnostics.Cannot_invoke_an_expression_whose_type_lacks_a_call_signature_Type_0_has_no_compatible_call_signatures : - Diagnostics.Cannot_use_new_with_an_expression_whose_type_lacks_a_call_or_construct_signature - ), typeToString(apparentType)); + function invocationErrorDetails(apparentType: Type, kind: SignatureKind): DiagnosticMessageChain { + let errorInfo: DiagnosticMessageChain | undefined; + const isCall = kind === SignatureKind.Call; + if (apparentType.flags & TypeFlags.Union) { + const types = (apparentType as UnionType).types; + let hasSignatures = false; + for (const constituent of types) { + const signatures = getSignaturesOfType(constituent, kind); + if (signatures.length !== 0) { + hasSignatures = true; + if (errorInfo) { + // Bail early if we already have an error, no chance of "No constituent of type is callable" + break; + } + } + else { + // Error on the first non callable constituent only + if (!errorInfo) { + errorInfo = chainDiagnosticMessages( + errorInfo, + isCall ? + Diagnostics.Type_0_has_no_call_signatures : + Diagnostics.Type_0_has_no_construct_signatures, + typeToString(constituent) + ); + errorInfo = chainDiagnosticMessages( + errorInfo, + isCall ? + Diagnostics.Not_all_constituents_of_type_0_are_callable : + Diagnostics.Not_all_constituents_of_type_0_are_constructable, + typeToString(apparentType) + ); + } + if (hasSignatures) { + // Bail early if we already found a siganture, no chance of "No constituent of type is callable" + break; + } + } + } + if (!hasSignatures) { + errorInfo = chainDiagnosticMessages( + /* detials */ undefined, + isCall ? + Diagnostics.No_constituent_of_type_0_is_callable : + Diagnostics.No_constituent_of_type_0_is_constructable, + typeToString(apparentType) + ); + } + if (!errorInfo) { + errorInfo = chainDiagnosticMessages( + errorInfo, + isCall ? + Diagnostics.Each_member_of_the_union_type_0_has_signatures_but_none_of_those_signatures_are_compatible_with_each_other : + Diagnostics.Each_member_of_the_union_type_0_has_construct_signatures_but_none_of_those_signatures_are_compatible_with_each_other, + typeToString(apparentType) + ); + } + } + else { + errorInfo = chainDiagnosticMessages( + errorInfo, + isCall ? + Diagnostics.Type_0_has_no_call_signatures : + Diagnostics.Type_0_has_no_construct_signatures, + typeToString(apparentType) + ); + } + return chainDiagnosticMessages( + errorInfo, + isCall ? + Diagnostics.This_expression_is_not_callable : + Diagnostics.This_expression_is_not_constructable + ); + } + function invocationError(errorTarget: Node, apparentType: Type, kind: SignatureKind, relatedInformation?: DiagnosticRelatedInformation) { + const diagnostic = createDiagnosticForNodeFromMessageChain(errorTarget, invocationErrorDetails(apparentType, kind)); + if (isCallExpression(errorTarget.parent)) { + const { start, length } = getDiagnosticSpanForCallNode(errorTarget.parent, /* doNotIncludeArguments */ true); + diagnostic.start = start; + diagnostic.length = length; + } + diagnostics.add(diagnostic); invocationErrorRecovery(apparentType, kind, relatedInformation ? addRelatedInfo(diagnostic, relatedInformation) : diagnostic); } @@ -21820,7 +22162,7 @@ namespace ts { } if (!callSignatures.length) { - invocationError(node, apparentType, SignatureKind.Call); + invocationError(node.tag, apparentType, SignatureKind.Call); return resolveErrorCall(node); } @@ -21876,9 +22218,9 @@ namespace ts { const headMessage = getDiagnosticHeadMessageForDecoratorResolution(node); if (!callSignatures.length) { - let errorInfo = chainDiagnosticMessages(/*details*/ undefined, Diagnostics.Cannot_invoke_an_expression_whose_type_lacks_a_call_signature_Type_0_has_no_compatible_call_signatures, typeToString(apparentType)); + let errorInfo = invocationErrorDetails(apparentType, SignatureKind.Call); errorInfo = chainDiagnosticMessages(errorInfo, headMessage); - const diag = createDiagnosticForNodeFromMessageChain(node, errorInfo); + const diag = createDiagnosticForNodeFromMessageChain(node.expression, errorInfo); diagnostics.add(diag); invocationErrorRecovery(apparentType, SignatureKind.Call, diag); return resolveErrorCall(node); @@ -22273,7 +22615,10 @@ namespace ts { case SyntaxKind.ElementAccessExpression: const expr = (node).expression; if (isIdentifier(expr)) { - const symbol = getSymbolAtLocation(expr); + let symbol = getSymbolAtLocation(expr); + if (symbol && symbol.flags & SymbolFlags.Alias) { + symbol = resolveAlias(symbol); + } return !!(symbol && (symbol.flags & SymbolFlags.Enum) && getEnumKind(symbol) === EnumKind.Literal); } } @@ -22912,12 +23257,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); @@ -22927,6 +23278,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); @@ -22947,8 +23302,6 @@ namespace ts { checkSignatureDeclaration(node); } } - - return type; } function getReturnOrPromisedType(node: FunctionLikeDeclaration | MethodSignature, functionFlags: FunctionFlags) { @@ -23331,14 +23684,16 @@ namespace ts { if (strictNullChecks && properties.length === 0) { return checkNonNullType(sourceType, node); } - for (const p of properties) { - checkObjectLiteralDestructuringPropertyAssignment(sourceType, p, properties, rightIsThis); + for (let i = 0; i < properties.length; i++) { + checkObjectLiteralDestructuringPropertyAssignment(node, sourceType, i, properties, rightIsThis); } return sourceType; } /** Note: If property cannot be a SpreadAssignment, then allProperties does not need to be provided */ - function checkObjectLiteralDestructuringPropertyAssignment(objectLiteralType: Type, property: ObjectLiteralElementLike, allProperties?: NodeArray, rightIsThis = false) { + function checkObjectLiteralDestructuringPropertyAssignment(node: ObjectLiteralExpression, objectLiteralType: Type, propertyIndex: number, allProperties?: NodeArray, rightIsThis = false) { + const properties = node.properties; + const property = properties[propertyIndex]; if (property.kind === SyntaxKind.PropertyAssignment || property.kind === SyntaxKind.ShorthandPropertyAssignment) { const name = property.name; const exprType = getLiteralTypeFromPropertyName(name); @@ -23355,18 +23710,25 @@ namespace ts { return checkDestructuringAssignment(property.kind === SyntaxKind.ShorthandPropertyAssignment ? property : property.initializer, type); } else if (property.kind === SyntaxKind.SpreadAssignment) { - if (languageVersion < ScriptTarget.ESNext) { - checkExternalEmitHelpers(property, ExternalEmitHelpers.Rest); + if (propertyIndex < properties.length - 1) { + error(property, Diagnostics.A_rest_element_must_be_last_in_a_destructuring_pattern); } - const nonRestNames: PropertyName[] = []; - if (allProperties) { - for (let i = 0; i < allProperties.length - 1; i++) { - nonRestNames.push(allProperties[i].name!); + else { + if (languageVersion < ScriptTarget.ESNext) { + checkExternalEmitHelpers(property, ExternalEmitHelpers.Rest); } + const nonRestNames: PropertyName[] = []; + if (allProperties) { + for (const otherProperty of allProperties) { + if (!isSpreadAssignment(otherProperty)) { + nonRestNames.push(otherProperty.name); + } + } + } + const type = getRestType(objectLiteralType, nonRestNames, objectLiteralType.symbol); + checkGrammarForDisallowedTrailingComma(allProperties, Diagnostics.A_rest_parameter_or_binding_pattern_may_not_have_a_trailing_comma); + return checkDestructuringAssignment(property.expression, type); } - const type = getRestType(objectLiteralType, nonRestNames, objectLiteralType.symbol); - checkGrammarForDisallowedTrailingComma(allProperties, Diagnostics.A_rest_parameter_or_binding_pattern_may_not_have_a_trailing_comma); - return checkDestructuringAssignment(property.expression, type); } else { error(property, Diagnostics.Property_assignment_expected); @@ -24413,7 +24775,7 @@ namespace ts { const constraintType = getConstraintOfTypeParameter(typeParameter); const defaultType = getDefaultFromTypeParameter(typeParameter); if (constraintType && defaultType) { - checkTypeAssignableTo(defaultType, getTypeWithThisArgument(constraintType, defaultType), node.default, Diagnostics.Type_0_does_not_satisfy_the_constraint_1); + checkTypeAssignableTo(defaultType, getTypeWithThisArgument(instantiateType(constraintType, makeUnaryTypeMapper(typeParameter, defaultType)), defaultType), node.default, Diagnostics.Type_0_does_not_satisfy_the_constraint_1); } if (produceDiagnostics) { checkTypeNameIsReserved(node.name, Diagnostics.Type_parameter_name_cannot_be_0); @@ -25082,7 +25444,7 @@ namespace ts { forEach(node.types, checkSourceElement); } - function checkIndexedAccessIndexType(type: Type, accessNode: Node) { + function checkIndexedAccessIndexType(type: Type, accessNode: IndexedAccessTypeNode | ElementAccessExpression) { if (!(type.flags & TypeFlags.IndexedAccess)) { return type; } @@ -25098,9 +25460,20 @@ namespace ts { } // Check if we're indexing with a numeric type and if either object or index types // is a generic type with a constraint that has a numeric index signature. - if (getIndexInfoOfType(getApparentType(objectType), IndexKind.Number) && isTypeAssignableToKind(indexType, TypeFlags.NumberLike)) { + const apparentObjectType = getApparentType(objectType); + if (getIndexInfoOfType(apparentObjectType, IndexKind.Number) && isTypeAssignableToKind(indexType, TypeFlags.NumberLike)) { return type; } + if (isGenericObjectType(objectType)) { + const propertyName = getPropertyNameFromIndex(indexType, accessNode); + if (propertyName) { + const propertySymbol = forEachType(apparentObjectType, t => getPropertyOfType(t, propertyName)); + if (propertySymbol && getDeclarationModifierFlagsFromSymbol(propertySymbol) & ModifierFlags.NonPublicAccessibilityModifier) { + error(accessNode, Diagnostics.Private_or_protected_member_0_cannot_be_accessed_on_a_type_parameter, unescapeLeadingUnderscores(propertyName)); + return errorType; + } + } + } error(accessNode, Diagnostics.Type_0_cannot_be_used_to_index_type_1, typeToString(indexType), typeToString(objectType)); return errorType; } @@ -25497,7 +25870,7 @@ namespace ts { case SyntaxKind.ImportSpecifier: // https://github.com/Microsoft/TypeScript/pull/7591 return DeclarationSpaces.ExportValue; default: - return Debug.fail(Debug.showSyntaxKind(d)); + return Debug.failBadSyntaxKind(d); } } } @@ -26800,7 +27173,7 @@ namespace ts { if (type !== errorType && declarationType !== errorType && !isTypeIdenticalTo(type, declarationType) && !(symbol.flags & SymbolFlags.Assignment)) { - errorNextVariableOrPropertyDeclarationMustHaveSameType(type, node, declarationType); + errorNextVariableOrPropertyDeclarationMustHaveSameType(symbol.valueDeclaration, type, node, declarationType); } if (node.initializer) { checkTypeAssignableToAndOptionallyElaborate(checkExpressionCached(node.initializer), declarationType, node, node.initializer, /*headMessage*/ undefined); @@ -26820,17 +27193,24 @@ namespace ts { } } - function errorNextVariableOrPropertyDeclarationMustHaveSameType(firstType: Type, nextDeclaration: Declaration, nextType: Type): void { + function errorNextVariableOrPropertyDeclarationMustHaveSameType(firstDeclaration: Declaration | undefined, firstType: Type, nextDeclaration: Declaration, nextType: Type): void { const nextDeclarationName = getNameOfDeclaration(nextDeclaration); const message = nextDeclaration.kind === SyntaxKind.PropertyDeclaration || nextDeclaration.kind === SyntaxKind.PropertySignature ? Diagnostics.Subsequent_property_declarations_must_have_the_same_type_Property_0_must_be_of_type_1_but_here_has_type_2 : Diagnostics.Subsequent_variable_declarations_must_have_the_same_type_Variable_0_must_be_of_type_1_but_here_has_type_2; - error( + const declName = declarationNameToString(nextDeclarationName); + const err = error( nextDeclarationName, message, - declarationNameToString(nextDeclarationName), + declName, typeToString(firstType), - typeToString(nextType)); + typeToString(nextType) + ); + if (firstDeclaration) { + addRelatedInfo(err, + createDiagnosticForNode(firstDeclaration, Diagnostics._0_was_also_declared_here, declName) + ); + } } function areDeclarationFlagsIdentical(left: Declaration, right: Declaration) { @@ -28257,9 +28637,9 @@ namespace ts { if (member.initializer) { return computeConstantValue(member); } - // In ambient enum declarations that specify no const modifier, enum member declarations that omit - // a value are considered computed members (as opposed to having auto-incremented values). - if (member.parent.flags & NodeFlags.Ambient && !isEnumConst(member.parent)) { + // In ambient non-const numeric enum declarations, enum members without initializers are + // considered computed members (as opposed to having auto-incremented values). + if (member.parent.flags & NodeFlags.Ambient && !isEnumConst(member.parent) && getEnumKind(getSymbolOfNode(member.parent)) === EnumKind.Numeric) { return undefined; } // If the member declaration specifies no value, the member is considered a constant enum member. @@ -29900,7 +30280,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) { @@ -29918,16 +30298,17 @@ namespace ts { // If this is from nested object binding pattern // for ({ skills: { primary, secondary } } = multiRobot, i = 0; i < 1; i++) { if (expr.parent.kind === SyntaxKind.PropertyAssignment) { - const typeOfParentObjectLiteral = getTypeOfArrayLiteralOrObjectLiteralDestructuringAssignment(expr.parent.parent); - return checkObjectLiteralDestructuringPropertyAssignment(typeOfParentObjectLiteral || errorType, expr.parent)!; // TODO: GH#18217 + const node = cast(expr.parent.parent, isObjectLiteralExpression); + const typeOfParentObjectLiteral = getTypeOfAssignmentPattern(node) || errorType; + const propertyIndex = indexOfNode(node.properties, expr.parent); + 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 @@ -29938,7 +30319,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); } @@ -30123,12 +30504,17 @@ namespace ts { return undefined; } + function isSymbolOfDestructuredElementOfCatchBinding(symbol: Symbol) { + return isBindingElement(symbol.valueDeclaration) + && walkUpBindingElementsAndPatterns(symbol.valueDeclaration).parent.kind === SyntaxKind.CatchClause; + } + function isSymbolOfDeclarationWithCollidingName(symbol: Symbol): boolean { if (symbol.flags & SymbolFlags.BlockScoped && !isSourceFile(symbol.valueDeclaration)) { const links = getSymbolLinks(symbol); if (links.isDeclarationWithCollidingName === undefined) { const container = getEnclosingBlockScopeContainer(symbol.valueDeclaration); - if (isStatementWithLocals(container)) { + if (isStatementWithLocals(container) || isSymbolOfDestructuredElementOfCatchBinding(symbol)) { const nodeLinks = getNodeLinks(symbol.valueDeclaration); if (resolveName(container.parent, symbol.escapedName, SymbolFlags.Value, /*nameNotFoundMessage*/ undefined, /*nameArg*/ undefined, /*isUse*/ false)) { // redeclaration - always should be renamed @@ -30891,6 +31277,7 @@ namespace ts { case ExternalEmitHelpers.Values: return "__values"; case ExternalEmitHelpers.Read: return "__read"; case ExternalEmitHelpers.Spread: return "__spread"; + case ExternalEmitHelpers.SpreadArrays: return "__spreadArrays"; case ExternalEmitHelpers.Await: return "__await"; case ExternalEmitHelpers.AsyncGenerator: return "__asyncGenerator"; case ExternalEmitHelpers.AsyncDelegator: return "__asyncDelegator"; @@ -31634,6 +32021,12 @@ namespace ts { } } + function checkGrammarJsxExpression(node: JsxExpression) { + if (node.expression && isCommaSequence(node.expression)) { + return grammarErrorOnNode(node.expression, Diagnostics.JSX_expressions_may_not_use_the_comma_operator_Did_you_mean_to_write_an_array); + } + } + function checkGrammarForInOrForOfStatement(forInOrOfStatement: ForInOrOfStatement): boolean { if (checkGrammarStatementInAmbientContext(forInOrOfStatement)) { return true; diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index fbc6d8d6fea..4747b89f08c 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -147,6 +147,12 @@ namespace ts { category: Diagnostics.Basic_Options, description: Diagnostics.Enable_incremental_compilation, }, + { + name: "locale", + type: "string", + category: Diagnostics.Advanced_Options, + description: Diagnostics.The_locale_used_when_displaying_messages_to_the_user_e_g_en_us + }, ]; /* @internal */ @@ -239,6 +245,7 @@ namespace ts { esnext: ModuleKind.ESNext }), affectsModuleResolution: true, + affectsEmit: true, paramType: Diagnostics.KIND, showInSimplifiedHelpView: true, category: Diagnostics.Basic_Options, @@ -584,6 +591,7 @@ namespace ts { name: "esModuleInterop", type: "boolean", affectsSemanticDiagnostics: true, + affectsEmit: true, showInSimplifiedHelpView: true, category: Diagnostics.Module_Resolution_Options, description: Diagnostics.Enables_emit_interoperability_between_CommonJS_and_ES_Modules_via_creation_of_namespace_objects_for_all_imports_Implies_allowSyntheticDefaultImports @@ -698,12 +706,6 @@ namespace ts { category: Diagnostics.Advanced_Options, description: Diagnostics.Emit_a_UTF_8_Byte_Order_Mark_BOM_in_the_beginning_of_output_files }, - { - name: "locale", - type: "string", - category: Diagnostics.Advanced_Options, - description: Diagnostics.The_locale_used_when_displaying_messages_to_the_user_e_g_en_us - }, { name: "newLine", type: createMapFromTemplate({ @@ -969,7 +971,8 @@ namespace ts { return typeAcquisition; } - function getOptionNameMap(): OptionNameMap { + /* @internal */ + export function getOptionNameMap(): OptionNameMap { return optionNameMapCache || (optionNameMapCache = createOptionNameMap(optionDeclarations)); } @@ -1022,8 +1025,7 @@ namespace ts { } } - /* @internal */ - export interface OptionsBase { + interface OptionsBase { [option: string]: CompilerOptionsValue | undefined; } @@ -1172,7 +1174,7 @@ namespace ts { export interface ParsedBuildCommand { buildOptions: BuildOptions; projects: string[]; - errors: ReadonlyArray; + errors: Diagnostic[]; } /*@internal*/ @@ -2361,7 +2363,7 @@ namespace ts { if (!host.fileExists(extendedConfigPath) && !endsWith(extendedConfigPath, Extension.Json)) { extendedConfigPath = `${extendedConfigPath}.json`; if (!host.fileExists(extendedConfigPath)) { - errors.push(createDiagnostic(Diagnostics.File_0_does_not_exist, extendedConfig)); + errors.push(createDiagnostic(Diagnostics.File_0_not_found, extendedConfig)); return undefined; } } @@ -2372,7 +2374,7 @@ namespace ts { if (resolved.resolvedModule) { return resolved.resolvedModule.resolvedFileName; } - errors.push(createDiagnostic(Diagnostics.File_0_does_not_exist, extendedConfig)); + errors.push(createDiagnostic(Diagnostics.File_0_not_found, extendedConfig)); return undefined; } diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 2dfc7acf188..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`; } @@ -1686,89 +1686,6 @@ namespace ts { export type AnyFunction = (...args: never[]) => void; export type AnyConstructor = new (...args: unknown[]) => unknown; - export namespace Debug { - export let currentAssertionLevel = AssertionLevel.None; - export let isDebugging = false; - - export function shouldAssert(level: AssertionLevel): boolean { - return currentAssertionLevel >= level; - } - - export function assert(expression: boolean, message?: string, verboseDebugInfo?: string | (() => string), stackCrawlMark?: AnyFunction): void { - if (!expression) { - if (verboseDebugInfo) { - message += "\r\nVerbose Debug Information: " + (typeof verboseDebugInfo === "string" ? verboseDebugInfo : verboseDebugInfo()); - } - fail(message ? "False expression: " + message : "False expression.", stackCrawlMark || assert); - } - } - - export function assertEqual(a: T, b: T, msg?: string, msg2?: string): void { - if (a !== b) { - const message = msg ? msg2 ? `${msg} ${msg2}` : msg : ""; - fail(`Expected ${a} === ${b}. ${message}`); - } - } - - export function assertLessThan(a: number, b: number, msg?: string): void { - if (a >= b) { - fail(`Expected ${a} < ${b}. ${msg || ""}`); - } - } - - export function assertLessThanOrEqual(a: number, b: number): void { - if (a > b) { - fail(`Expected ${a} <= ${b}`); - } - } - - export function assertGreaterThanOrEqual(a: number, b: number): void { - if (a < b) { - fail(`Expected ${a} >= ${b}`); - } - } - - export function fail(message?: string, stackCrawlMark?: AnyFunction): never { - debugger; - const e = new Error(message ? `Debug Failure. ${message}` : "Debug Failure."); - if ((Error).captureStackTrace) { - (Error).captureStackTrace(e, stackCrawlMark || fail); - } - throw e; - } - - export function assertDefined(value: T | null | undefined, message?: string): T { - if (value === undefined || value === null) return fail(message); - return value; - } - - export function assertEachDefined>(value: A, message?: string): A { - for (const v of value) { - assertDefined(v, message); - } - return value; - } - - export function assertNever(member: never, message = "Illegal value:", stackCrawlMark?: AnyFunction): never { - const detail = typeof member === "object" && "kind" in member && "pos" in member ? "SyntaxKind: " + showSyntaxKind(member as Node) : JSON.stringify(member); - return fail(`${message} ${detail}`, stackCrawlMark || assertNever); - } - - export function getFunctionName(func: AnyFunction) { - if (typeof func !== "function") { - return ""; - } - else if (func.hasOwnProperty("name")) { - return (func).name; - } - else { - const text = Function.prototype.toString.call(func); - const match = /^function\s+([\w\$]+)\s*\(/.exec(text); - return match ? match[1] : ""; - } - } - } - export function equateValues(a: T, b: T) { return a === b; } diff --git a/src/compiler/debug.ts b/src/compiler/debug.ts new file mode 100644 index 00000000000..ee6847133c1 --- /dev/null +++ b/src/compiler/debug.ts @@ -0,0 +1,261 @@ +/* @internal */ +namespace ts { + export namespace Debug { + export let currentAssertionLevel = AssertionLevel.None; + export let isDebugging = false; + + export function shouldAssert(level: AssertionLevel): boolean { + return currentAssertionLevel >= level; + } + + export function assert(expression: boolean, message?: string, verboseDebugInfo?: string | (() => string), stackCrawlMark?: AnyFunction): void { + if (!expression) { + if (verboseDebugInfo) { + message += "\r\nVerbose Debug Information: " + (typeof verboseDebugInfo === "string" ? verboseDebugInfo : verboseDebugInfo()); + } + fail(message ? "False expression: " + message : "False expression.", stackCrawlMark || assert); + } + } + + export function assertEqual(a: T, b: T, msg?: string, msg2?: string): void { + if (a !== b) { + const message = msg ? msg2 ? `${msg} ${msg2}` : msg : ""; + fail(`Expected ${a} === ${b}. ${message}`); + } + } + + export function assertLessThan(a: number, b: number, msg?: string): void { + if (a >= b) { + fail(`Expected ${a} < ${b}. ${msg || ""}`); + } + } + + export function assertLessThanOrEqual(a: number, b: number): void { + if (a > b) { + fail(`Expected ${a} <= ${b}`); + } + } + + export function assertGreaterThanOrEqual(a: number, b: number): void { + if (a < b) { + fail(`Expected ${a} >= ${b}`); + } + } + + export function fail(message?: string, stackCrawlMark?: AnyFunction): never { + debugger; + const e = new Error(message ? `Debug Failure. ${message}` : "Debug Failure."); + if ((Error).captureStackTrace) { + (Error).captureStackTrace(e, stackCrawlMark || fail); + } + throw e; + } + + export function assertDefined(value: T | null | undefined, message?: string): T { + if (value === undefined || value === null) return fail(message); + return value; + } + + export function assertEachDefined>(value: A, message?: string): A { + for (const v of value) { + assertDefined(v, message); + } + return value; + } + + export function assertNever(member: never, message = "Illegal value:", stackCrawlMark?: AnyFunction): never { + const detail = typeof member === "object" && "kind" in member && "pos" in member && formatSyntaxKind ? "SyntaxKind: " + formatSyntaxKind((member as Node).kind) : JSON.stringify(member); + return fail(`${message} ${detail}`, stackCrawlMark || assertNever); + } + + export function getFunctionName(func: AnyFunction) { + if (typeof func !== "function") { + return ""; + } + else if (func.hasOwnProperty("name")) { + return (func).name; + } + else { + const text = Function.prototype.toString.call(func); + const match = /^function\s+([\w\$]+)\s*\(/.exec(text); + return match ? match[1] : ""; + } + } + + export function formatSymbol(symbol: Symbol): string { + return `{ name: ${unescapeLeadingUnderscores(symbol.escapedName)}; flags: ${formatSymbolFlags(symbol.flags)}; declarations: ${map(symbol.declarations, node => formatSyntaxKind(node.kind))} }`; + } + + /** + * Formats an enum value as a string for debugging and debug assertions. + */ + export function formatEnum(value = 0, enumObject: any, isFlags?: boolean) { + const members = getEnumMembers(enumObject); + if (value === 0) { + return members.length > 0 && members[0][0] === 0 ? members[0][1] : "0"; + } + if (isFlags) { + let result = ""; + let remainingFlags = value; + for (let i = members.length - 1; i >= 0 && remainingFlags !== 0; i--) { + const [enumValue, enumName] = members[i]; + if (enumValue !== 0 && (remainingFlags & enumValue) === enumValue) { + remainingFlags &= ~enumValue; + result = `${enumName}${result ? "|" : ""}${result}`; + } + } + if (remainingFlags === 0) { + return result; + } + } + else { + for (const [enumValue, enumName] of members) { + if (enumValue === value) { + return enumName; + } + } + } + return value.toString(); + } + + function getEnumMembers(enumObject: any) { + const result: [number, string][] = []; + for (const name in enumObject) { + const value = enumObject[name]; + if (typeof value === "number") { + result.push([value, name]); + } + } + + return stableSort<[number, string]>(result, (x, y) => compareValues(x[0], y[0])); + } + + export function formatSyntaxKind(kind: SyntaxKind | undefined): string { + return formatEnum(kind, (ts).SyntaxKind, /*isFlags*/ false); + } + + export function formatNodeFlags(flags: NodeFlags | undefined): string { + return formatEnum(flags, (ts).NodeFlags, /*isFlags*/ true); + } + + export function formatModifierFlags(flags: ModifierFlags | undefined): string { + return formatEnum(flags, (ts).ModifierFlags, /*isFlags*/ true); + } + + export function formatTransformFlags(flags: TransformFlags | undefined): string { + return formatEnum(flags, (ts).TransformFlags, /*isFlags*/ true); + } + + export function formatEmitFlags(flags: EmitFlags | undefined): string { + return formatEnum(flags, (ts).EmitFlags, /*isFlags*/ true); + } + + export function formatSymbolFlags(flags: SymbolFlags | undefined): string { + return formatEnum(flags, (ts).SymbolFlags, /*isFlags*/ true); + } + + export function formatTypeFlags(flags: TypeFlags | undefined): string { + return formatEnum(flags, (ts).TypeFlags, /*isFlags*/ true); + } + + export function formatObjectFlags(flags: ObjectFlags | undefined): string { + return formatEnum(flags, (ts).ObjectFlags, /*isFlags*/ true); + } + + export function failBadSyntaxKind(node: Node, message?: string): never { + return fail( + `${message || "Unexpected node."}\r\nNode ${formatSyntaxKind(node.kind)} was unexpected.`, + failBadSyntaxKind); + } + + export const assertEachNode = shouldAssert(AssertionLevel.Normal) + ? (nodes: Node[], test: (node: Node) => boolean, message?: string): void => assert( + test === undefined || every(nodes, test), + message || "Unexpected node.", + () => `Node array did not pass test '${getFunctionName(test)}'.`, + assertEachNode) + : noop; + + export const assertNode = shouldAssert(AssertionLevel.Normal) + ? (node: Node | undefined, test: ((node: Node | undefined) => boolean) | undefined, message?: string): void => assert( + test === undefined || test(node), + message || "Unexpected node.", + () => `Node ${formatSyntaxKind(node!.kind)} did not pass test '${getFunctionName(test!)}'.`, + assertNode) + : noop; + + export const assertOptionalNode = shouldAssert(AssertionLevel.Normal) + ? (node: Node, test: (node: Node) => boolean, message?: string): void => assert( + test === undefined || node === undefined || test(node), + message || "Unexpected node.", + () => `Node ${formatSyntaxKind(node.kind)} did not pass test '${getFunctionName(test)}'.`, + assertOptionalNode) + : noop; + + export const assertOptionalToken = shouldAssert(AssertionLevel.Normal) + ? (node: Node, kind: SyntaxKind, message?: string): void => assert( + kind === undefined || node === undefined || node.kind === kind, + message || "Unexpected node.", + () => `Node ${formatSyntaxKind(node.kind)} was not a '${formatSyntaxKind(kind)}' token.`, + assertOptionalToken) + : noop; + + export const assertMissingNode = shouldAssert(AssertionLevel.Normal) + ? (node: Node, message?: string): void => assert( + node === undefined, + message || "Unexpected node.", + () => `Node ${formatSyntaxKind(node.kind)} was unexpected'.`, + assertMissingNode) + : noop; + + let isDebugInfoEnabled = false; + + /** + * Injects debug information into frequently used types. + */ + export function enableDebugInfo() { + if (isDebugInfoEnabled) return; + + // Add additional properties in debug mode to assist with debugging. + Object.defineProperties(objectAllocator.getSymbolConstructor().prototype, { + __debugFlags: { get(this: Symbol) { return formatSymbolFlags(this.flags); } } + }); + + Object.defineProperties(objectAllocator.getTypeConstructor().prototype, { + __debugFlags: { get(this: Type) { return formatTypeFlags(this.flags); } }, + __debugObjectFlags: { get(this: Type) { return this.flags & TypeFlags.Object ? formatObjectFlags((this).objectFlags) : ""; } }, + __debugTypeToString: { value(this: Type) { return this.checker.typeToString(this); } }, + }); + + const nodeConstructors = [ + objectAllocator.getNodeConstructor(), + objectAllocator.getIdentifierConstructor(), + objectAllocator.getTokenConstructor(), + objectAllocator.getSourceFileConstructor() + ]; + + for (const ctor of nodeConstructors) { + if (!ctor.prototype.hasOwnProperty("__debugKind")) { + Object.defineProperties(ctor.prototype, { + __debugKind: { get(this: Node) { return formatSyntaxKind(this.kind); } }, + __debugNodeFlags: { get(this: Node) { return formatNodeFlags(this.flags); } }, + __debugModifierFlags: { get(this: Node) { return formatModifierFlags(getModifierFlagsNoCache(this)); } }, + __debugTransformFlags: { get(this: Node) { return formatTransformFlags(this.transformFlags); } }, + __debugIsParseTreeNode: { get(this: Node) { return isParseTreeNode(this); } }, + __debugEmitFlags: { get(this: Node) { return formatEmitFlags(getEmitFlags(this)); } }, + __debugGetText: { + value(this: Node, includeTrivia?: boolean) { + if (nodeIsSynthesized(this)) return ""; + const parseNode = getParseTreeNode(this); + const sourceFile = parseNode && getSourceFileOfNode(parseNode); + return sourceFile ? getSourceTextOfNodeFromSourceFile(sourceFile, parseNode, includeTrivia) : ""; + } + } + }); + } + } + + isDebugInfoEnabled = true; + } + } +} \ No newline at end of file diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 2db71a6d266..ef502ecbcef 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -655,7 +655,7 @@ "category": "Error", "code": 1207 }, - "Cannot compile namespaces when the '--isolatedModules' flag is provided.": { + "All files must be modules when the '--isolatedModules' flag is provided.": { "category": "Error", "code": 1208 }, @@ -1236,7 +1236,7 @@ "category": "Error", "code": 2348 }, - "Cannot invoke an expression whose type lacks a call signature. Type '{0}' has no compatible call signatures.": { + "This expression is not callable.": { "category": "Error", "code": 2349 }, @@ -1244,7 +1244,7 @@ "category": "Error", "code": 2350 }, - "Cannot use 'new' with an expression whose type lacks a call or construct signature.": { + "This expression is not constructable.": { "category": "Error", "code": 2351 }, @@ -2621,6 +2621,38 @@ "category": "Error", "code": 2754 }, + "No constituent of type '{0}' is callable.": { + "category": "Error", + "code": 2755 + }, + "Not all constituents of type '{0}' are callable.": { + "category": "Error", + "code": 2756 + }, + "Type '{0}' has no call signatures.": { + "category": "Error", + "code": 2757 + }, + "Each member of the union type '{0}' has signatures, but none of those signatures are compatible with each other.": { + "category": "Error", + "code": 2758 + }, + "No constituent of type '{0}' is constructable.": { + "category": "Error", + "code": 2759 + }, + "Not all constituents of type '{0}' are constructable.": { + "category": "Error", + "code": 2760 + }, + "Type '{0}' has no construct signatures.": { + "category": "Error", + "code": 2761 + }, + "Each member of the union type '{0}' has construct signatures, but none of those signatures are compatible with each other.": { + "category": "Error", + "code": 2762 + }, "Import declaration '{0}' is using private name '{1}'.": { "category": "Error", @@ -2963,6 +2995,10 @@ "category": "Error", "code": 4104 }, + "Private or protected member '{0}' cannot be accessed on a type parameter.": { + "category": "Error", + "code": 4105 + }, "The current host does not support the '{0}' option.": { "category": "Error", @@ -3811,10 +3847,6 @@ "category": "Error", "code": 6189 }, - "Found 'package.json' at '{0}'. Package ID is '{1}'.": { - "category": "Message", - "code": 6190 - }, "Whether to keep outdated console output in watch mode instead of clearing the screen.": { "category": "Message", "code": 6191 @@ -3923,6 +3955,18 @@ "category": "Message", "code": 6217 }, + "======== Module name '{0}' was successfully resolved to '{1}' with Package ID '{2}'. ========": { + "category": "Message", + "code": 6218 + }, + "======== Type reference directive '{0}' was successfully resolved to '{1}' with Package ID '{2}', primary: {3}. ========": { + "category": "Message", + "code": 6219 + }, + "'package.json' had a falsy '{0}' field.": { + "category": "Message", + "code": 6220 + }, "Projects to reference": { "category": "Message", @@ -3948,7 +3992,7 @@ "category": "Error", "code": 6306 }, - "File '{0}' is not in project file list. Projects must list all files or use an 'include' pattern.": { + "File '{0}' is not listed within the file list of project '{1}'. Projects must list all files or use an 'include' pattern.": { "category": "Error", "code": 6307 }, @@ -4284,6 +4328,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 @@ -4974,14 +5026,18 @@ "category": "Message", "code": 95079 }, - "Add 'const' to unresolved variable": { + "Infer 'this' type of '{0}' from usage": { "category": "Message", "code": 95080 }, - "Add 'const' to all unresolved variables": { + "Add 'const' to unresolved variable": { "category": "Message", "code": 95081 }, + "Add 'const' to all unresolved variables": { + "category": "Message", + "code": 95082 + }, "No value exists in scope for the shorthand property '{0}'. Either declare one or provide an initializer.": { "category": "Error", "code": 18004 @@ -4993,5 +5049,9 @@ "Classes may not have a field named 'constructor'.": { "category": "Error", "code": 18006 + }, + "JSX expressions may not use the comma operator. Did you mean to write an array?": { + "category": "Error", + "code": 18007 } } diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index d335ceb1b60..a1207c9b892 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -249,14 +249,16 @@ namespace ts { }; function emitSourceFileOrBundle({ jsFilePath, sourceMapFilePath, declarationFilePath, declarationMapPath, buildInfoPath }: EmitFileNames, sourceFileOrBundle: SourceFile | Bundle | undefined) { + let buildInfoDirectory: string | undefined; if (buildInfoPath && sourceFileOrBundle && isBundle(sourceFileOrBundle)) { + buildInfoDirectory = getDirectoryPath(getNormalizedAbsolutePath(buildInfoPath, host.getCurrentDirectory())); bundleBuildInfo = { - commonSourceDirectory: host.getCommonSourceDirectory(), - sourceFiles: sourceFileOrBundle.sourceFiles.map(file => file.fileName) + commonSourceDirectory: relativeToBuildInfo(host.getCommonSourceDirectory()), + sourceFiles: sourceFileOrBundle.sourceFiles.map(file => relativeToBuildInfo(getNormalizedAbsolutePath(file.fileName, host.getCurrentDirectory()))) }; } - emitJsFileOrBundle(sourceFileOrBundle, jsFilePath, sourceMapFilePath); - emitDeclarationFileOrBundle(sourceFileOrBundle, declarationFilePath, declarationMapPath); + emitJsFileOrBundle(sourceFileOrBundle, jsFilePath, sourceMapFilePath, relativeToBuildInfo); + emitDeclarationFileOrBundle(sourceFileOrBundle, declarationFilePath, declarationMapPath, relativeToBuildInfo); emitBuildInfo(bundleBuildInfo, buildInfoPath); if (!emitSkipped && emittedFilesList) { @@ -278,13 +280,16 @@ namespace ts { emittedFilesList.push(declarationMapPath); } } + + function relativeToBuildInfo(path: string) { + return ensurePathIsNonModuleName(getRelativePathFromDirectory(buildInfoDirectory!, path, host.getCanonicalFileName)); + } } function emitBuildInfo(bundle: BundleBuildInfo | undefined, buildInfoPath: string | undefined) { // 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; @@ -292,7 +297,11 @@ namespace ts { writeFile(host, emitterDiagnostics, buildInfoPath, getBuildInfoText({ bundle, program, version }), /*writeByteOrderMark*/ false); } - function emitJsFileOrBundle(sourceFileOrBundle: SourceFile | Bundle | undefined, jsFilePath: string | undefined, sourceMapFilePath: string | undefined) { + function emitJsFileOrBundle( + sourceFileOrBundle: SourceFile | Bundle | undefined, + jsFilePath: string | undefined, + sourceMapFilePath: string | undefined, + relativeToBuildInfo: (path: string) => string) { if (!sourceFileOrBundle || emitOnlyDtsFiles || !jsFilePath) { return; } @@ -315,7 +324,8 @@ namespace ts { inlineSourceMap: compilerOptions.inlineSourceMap, inlineSources: compilerOptions.inlineSources, extendedDiagnostics: compilerOptions.extendedDiagnostics, - writeBundleFileInfo: !!bundleBuildInfo + writeBundleFileInfo: !!bundleBuildInfo, + relativeToBuildInfo }; // Create a printer to print the nodes @@ -336,7 +346,11 @@ namespace ts { if (bundleBuildInfo) bundleBuildInfo.js = printer.bundleFileInfo; } - function emitDeclarationFileOrBundle(sourceFileOrBundle: SourceFile | Bundle | undefined, declarationFilePath: string | undefined, declarationMapPath: string | undefined) { + function emitDeclarationFileOrBundle( + sourceFileOrBundle: SourceFile | Bundle | undefined, + declarationFilePath: string | undefined, + declarationMapPath: string | undefined, + relativeToBuildInfo: (path: string) => string) { if (!sourceFileOrBundle || !(declarationFilePath && !isInJSFile(sourceFileOrBundle))) { return; } @@ -367,7 +381,8 @@ namespace ts { extendedDiagnostics: compilerOptions.extendedDiagnostics, onlyPrintJsDocStyle: true, writeBundleFileInfo: !!bundleBuildInfo, - recordInternalSection: !!bundleBuildInfo + recordInternalSection: !!bundleBuildInfo, + relativeToBuildInfo }; const declarationPrinter = createPrinter(printerOptions, { @@ -614,10 +629,14 @@ namespace ts { getNewLine(): string; } - function createSourceFilesFromBundleBuildInfo(bundle: BundleBuildInfo): ReadonlyArray { + function createSourceFilesFromBundleBuildInfo(bundle: BundleBuildInfo, buildInfoDirectory: string, host: EmitUsingBuildInfoHost): ReadonlyArray { const sourceFiles = bundle.sourceFiles.map(fileName => { const sourceFile = createNode(SyntaxKind.SourceFile, 0, 0) as SourceFile; - sourceFile.fileName = fileName; + sourceFile.fileName = getRelativePathFromDirectory( + host.getCurrentDirectory(), + getNormalizedAbsolutePath(fileName, buildInfoDirectory), + !host.useCaseSensitiveFileNames() + ); sourceFile.text = ""; sourceFile.statements = createNodeArray(); return sourceFile; @@ -638,7 +657,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!; @@ -656,6 +680,7 @@ namespace ts { const buildInfo = getBuildInfo(buildInfoText); if (!buildInfo.bundle || !buildInfo.bundle.js || (declarationText && !buildInfo.bundle.dts)) return buildInfoPath!; + const buildInfoDirectory = getDirectoryPath(getNormalizedAbsolutePath(buildInfoPath!, host.getCurrentDirectory())); const ownPrependInput = createInputFiles( jsFileText, declarationText!, @@ -671,11 +696,11 @@ namespace ts { ); const outputFiles: OutputFile[] = []; const prependNodes = createPrependNodes(config.projectReferences, getCommandLine, f => host.readFile(f)); - const sourceFilesForJsEmit = createSourceFilesFromBundleBuildInfo(buildInfo.bundle); + const sourceFilesForJsEmit = createSourceFilesFromBundleBuildInfo(buildInfo.bundle, buildInfoDirectory, host); const emitHost: EmitHost = { getPrependNodes: memoize(() => [...prependNodes, ownPrependInput]), getCanonicalFileName: host.getCanonicalFileName, - getCommonSourceDirectory: () => buildInfo.bundle!.commonSourceDirectory, + getCommonSourceDirectory: () => getNormalizedAbsolutePath(buildInfo.bundle!.commonSourceDirectory, buildInfoDirectory), getCompilerOptions: () => config.options, getCurrentDirectory: () => host.getCurrentDirectory(), getNewLine: () => host.getNewLine(), @@ -721,9 +746,15 @@ namespace ts { fileExists: f => host.fileExists(f), directoryExists: host.directoryExists && (f => host.directoryExists!(f)), useCaseSensitiveFileNames: () => host.useCaseSensitiveFileNames(), - getProgramBuildInfo: returnUndefined + getProgramBuildInfo: returnUndefined, + getSourceFileFromReference: returnUndefined, }; - emitFiles(notImplementedResolver, emitHost, /*targetSourceFile*/ undefined, getTransformers(config.options), /*emitOnlyDtsFiles*/ false); + emitFiles( + notImplementedResolver, + emitHost, + /*targetSourceFile*/ undefined, + getTransformers(config.options, customTransformers) + ); return outputFiles; } @@ -765,6 +796,7 @@ namespace ts { let write = writeBase; let isOwnFileEmit: boolean; const bundleFileInfo = printerOptions.writeBundleFileInfo ? { sections: [] } as BundleFileInfo : undefined; + const relativeToBuildInfo = bundleFileInfo ? Debug.assertDefined(printerOptions.relativeToBuildInfo) : undefined; const recordInternalSection = printerOptions.recordInternalSection; let sourceFileTextPos = 0; let sourceFileTextKind: BundleFileTextLikeKind = BundleFileSectionKind.Text; @@ -933,7 +965,13 @@ namespace ts { if (prepend.oldFileOfCurrentEmit) bundleFileInfo.sections.push(...newSections); else { newSections.forEach(section => Debug.assert(isBundleFileTextLike(section))); - bundleFileInfo.sections.push({ pos, end: writer.getTextPos(), kind: BundleFileSectionKind.Prepend, data: (prepend as UnparsedSource).fileName, texts: newSections as BundleFileTextLike[] }); + bundleFileInfo.sections.push({ + pos, + end: writer.getTextPos(), + kind: BundleFileSectionKind.Prepend, + data: relativeToBuildInfo!((prepend as UnparsedSource).fileName), + texts: newSections as BundleFileTextLike[] + }); } } } @@ -4527,6 +4565,8 @@ namespace ts { case SyntaxKind.GetAccessor: case SyntaxKind.SetAccessor: return generateNameForMethodOrAccessor(node); + case SyntaxKind.ComputedPropertyName: + return makeTempVariableName(TempFlags.Auto, /*reserveInNestedScopes*/ true); default: return makeTempVariableName(TempFlags.Auto); } diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts index 517ebacb89d..f88950f531a 100644 --- a/src/compiler/factory.ts +++ b/src/compiler/factory.ts @@ -1544,8 +1544,8 @@ namespace ts { export function createIf(expression: Expression, thenStatement: Statement, elseStatement?: Statement) { const node = createSynthesizedNode(SyntaxKind.IfStatement); node.expression = expression; - node.thenStatement = thenStatement; - node.elseStatement = elseStatement; + node.thenStatement = asEmbeddedStatement(thenStatement); + node.elseStatement = asEmbeddedStatement(elseStatement); return node; } @@ -1559,7 +1559,7 @@ namespace ts { export function createDo(statement: Statement, expression: Expression) { const node = createSynthesizedNode(SyntaxKind.DoStatement); - node.statement = statement; + node.statement = asEmbeddedStatement(statement); node.expression = expression; return node; } @@ -1574,7 +1574,7 @@ namespace ts { export function createWhile(expression: Expression, statement: Statement) { const node = createSynthesizedNode(SyntaxKind.WhileStatement); node.expression = expression; - node.statement = statement; + node.statement = asEmbeddedStatement(statement); return node; } @@ -1590,7 +1590,7 @@ namespace ts { node.initializer = initializer; node.condition = condition; node.incrementor = incrementor; - node.statement = statement; + node.statement = asEmbeddedStatement(statement); return node; } @@ -1607,7 +1607,7 @@ namespace ts { const node = createSynthesizedNode(SyntaxKind.ForInStatement); node.initializer = initializer; node.expression = expression; - node.statement = statement; + node.statement = asEmbeddedStatement(statement); return node; } @@ -1624,7 +1624,7 @@ namespace ts { node.awaitModifier = awaitModifier; node.initializer = initializer; node.expression = expression; - node.statement = statement; + node.statement = asEmbeddedStatement(statement); return node; } @@ -1676,7 +1676,7 @@ namespace ts { export function createWith(expression: Expression, statement: Statement) { const node = createSynthesizedNode(SyntaxKind.WithStatement); node.expression = expression; - node.statement = statement; + node.statement = asEmbeddedStatement(statement); return node; } @@ -1704,7 +1704,7 @@ namespace ts { export function createLabel(label: string | Identifier, statement: Statement) { const node = createSynthesizedNode(SyntaxKind.LabeledStatement); node.label = asName(label); - node.statement = statement; + node.statement = asEmbeddedStatement(statement); return node; } @@ -2204,6 +2204,13 @@ namespace ts { return tag; } + /** @internal */ + export function createJSDocThisTag(typeExpression?: JSDocTypeExpression): JSDocThisTag { + const tag = createJSDocTag(SyntaxKind.JSDocThisTag, "this"); + tag.typeExpression = typeExpression; + return tag; + } + /* @internal */ export function createJSDocParamTag(name: EntityName, isBracketed: boolean, typeExpression?: JSDocTypeExpression, comment?: string): JSDocParameterTag { const tag = createJSDocTag(SyntaxKind.JSDocParameterTag, "param"); @@ -2657,6 +2664,7 @@ namespace ts { valuesHelper, readHelper, spreadHelper, + spreadArraysHelper, restHelper, decorateHelper, metadataHelper, @@ -3072,6 +3080,12 @@ namespace ts { return typeof value === "number" ? createToken(value) : value; } + function asEmbeddedStatement(statement: T): T | EmptyStatement; + function asEmbeddedStatement(statement: T | undefined): T | EmptyStatement | undefined; + function asEmbeddedStatement(statement: T | undefined): T | EmptyStatement | undefined { + return statement && isNotEmittedStatement(statement) ? setTextRange(setOriginalNode(createEmptyStatement(), statement), statement) : statement; + } + /** * Clears any EmitNode entries from parse-tree nodes. * @param sourceFile A source file. @@ -3118,6 +3132,18 @@ namespace ts { return node.emitNode; } + /** + * Sets `EmitFlags.NoComments` on a node and removes any leading and trailing synthetic comments. + * @internal + */ + export function removeAllComments(node: T): T { + const emitNode = getOrCreateEmitNode(node); + emitNode.flags |= EmitFlags.NoComments; + emitNode.leadingComments = undefined; + emitNode.trailingComments = undefined; + return node; + } + export function setTextRange(range: T, location: TextRange | undefined): T { if (location) { range.pos = location.pos; @@ -3693,6 +3719,31 @@ namespace ts { ); } + export const spreadArraysHelper: UnscopedEmitHelper = { + name: "typescript:spreadArrays", + scoped: false, + text: ` + var __spreadArrays = (this && this.__spreadArrays) || function () { + for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length; + for (var r = Array(s), k = 0, i = 0; i < il; i++) + for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++) + r[k] = a[j]; + return r; + };` + }; + + export function createSpreadArraysHelper(context: TransformationContext, argumentList: ReadonlyArray, location?: TextRange) { + context.requestEmitHelper(spreadArraysHelper); + return setTextRange( + createCall( + getHelperName("__spreadArrays"), + /*typeArguments*/ undefined, + argumentList + ), + location + ); + } + // Utilities export function createForOfBindingStatement(node: ForInitializer, boundValue: Expression): Statement { diff --git a/src/compiler/moduleNameResolver.ts b/src/compiler/moduleNameResolver.ts index 8761cc0a087..94f835caff5 100644 --- a/src/compiler/moduleNameResolver.ts +++ b/src/compiler/moduleNameResolver.ts @@ -16,12 +16,23 @@ namespace ts { push(value: T): void; } - function withPackageId(packageId: PackageId | undefined, r: PathAndExtension | undefined): Resolved | undefined { + function withPackageId(packageInfo: PackageJsonInfo | undefined, r: PathAndExtension | undefined): Resolved | undefined { + let packageId: PackageId | undefined; + if (r && packageInfo) { + const packageJsonContent = packageInfo.packageJsonContent as PackageJson; + if (typeof packageJsonContent.name === "string" && typeof packageJsonContent.version === "string") { + packageId = { + name: packageJsonContent.name, + subModuleName: r.path.slice(packageInfo.packageDirectory.length + directorySeparator.length), + version: packageJsonContent.version + }; + } + } return r && { path: r.path, extension: r.ext, packageId }; } function noPackageId(r: PathAndExtension | undefined): Resolved | undefined { - return withPackageId(/*packageId*/ undefined, r); + return withPackageId(/*packageInfo*/ undefined, r); } function removeIgnoredPackageId(r: Resolved | undefined): PathAndExtension | undefined { @@ -130,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); @@ -307,7 +326,12 @@ namespace ts { const { fileName, packageId } = resolved; const resolvedFileName = options.preserveSymlinks ? fileName : realPath(fileName, host, traceEnabled); if (traceEnabled) { - trace(host, Diagnostics.Type_reference_directive_0_was_successfully_resolved_to_1_primary_Colon_2, typeReferenceDirectiveName, resolvedFileName, primary); + if (packageId) { + trace(host, Diagnostics.Type_reference_directive_0_was_successfully_resolved_to_1_with_Package_ID_2_primary_Colon_3, typeReferenceDirectiveName, resolvedFileName, packageIdToString(packageId), primary); + } + else { + trace(host, Diagnostics.Type_reference_directive_0_was_successfully_resolved_to_1_primary_Colon_2, typeReferenceDirectiveName, resolvedFileName, primary); + } } resolvedTypeReferenceDirective = { primary, resolvedFileName, packageId, isExternalLibraryImport: pathContainsNodeModules(fileName) }; } @@ -663,7 +687,12 @@ namespace ts { if (traceEnabled) { if (result.resolvedModule) { - trace(host, Diagnostics.Module_name_0_was_successfully_resolved_to_1, moduleName, result.resolvedModule.resolvedFileName); + if (result.resolvedModule.packageId) { + trace(host, Diagnostics.Module_name_0_was_successfully_resolved_to_1_with_Package_ID_2, moduleName, result.resolvedModule.resolvedFileName, packageIdToString(result.resolvedModule.packageId)); + } + else { + trace(host, Diagnostics.Module_name_0_was_successfully_resolved_to_1, moduleName, result.resolvedModule.resolvedFileName); + } } else { trace(host, Diagnostics.Module_name_0_was_not_resolved, moduleName); @@ -968,10 +997,9 @@ namespace ts { } const resolvedFromFile = loadModuleFromFile(extensions, candidate, onlyRecordFailures, state); if (resolvedFromFile) { - const nm = considerPackageJson ? parseNodeModuleFromPath(resolvedFromFile) : undefined; - const packageInfo = nm && getPackageJsonInfo(nm.packageDirectory, nm.subModuleName, /*onlyRecordFailures*/ false, state); - const packageId = packageInfo && packageInfo.packageId; - return withPackageId(packageId, resolvedFromFile); + const packageDirectory = considerPackageJson ? parseNodeModuleFromPath(resolvedFromFile) : undefined; + const packageInfo = packageDirectory ? getPackageJsonInfo(packageDirectory, /*onlyRecordFailures*/ false, state) : undefined; + return withPackageId(packageInfo, resolvedFromFile); } } if (!onlyRecordFailures) { @@ -998,13 +1026,12 @@ namespace ts { * (Not neeeded for `loadModuleFromNodeModules` as that looks up the `package.json` as part of resolution.) * * packageDirectory is the directory of the package itself. - * subModuleName is the path within the package. - * For `blah/node_modules/foo/index.d.ts` this is { packageDirectory: "foo", subModuleName: "index.d.ts" }. (Part before "/node_modules/" is ignored.) - * For `/node_modules/foo/bar.d.ts` this is { packageDirectory: "foo", subModuleName": "bar/index.d.ts" }. - * For `/node_modules/@types/foo/bar/index.d.ts` this is { packageDirectory: "@types/foo", subModuleName: "bar/index.d.ts" }. - * For `/node_modules/foo/bar/index.d.ts` this is { packageDirectory: "foo", subModuleName": "bar/index.d.ts" }. + * For `blah/node_modules/foo/index.d.ts` this is packageDirectory: "foo" + * For `/node_modules/foo/bar.d.ts` this is packageDirectory: "foo" + * For `/node_modules/@types/foo/bar/index.d.ts` this is packageDirectory: "@types/foo" + * For `/node_modules/foo/bar/index.d.ts` this is packageDirectory: "foo" */ - function parseNodeModuleFromPath(resolved: PathAndExtension): { packageDirectory: string, subModuleName: string } | undefined { + function parseNodeModuleFromPath(resolved: PathAndExtension): string | undefined { const path = normalizePath(resolved.path); const idx = path.lastIndexOf(nodeModulesPathPart); if (idx === -1) { @@ -1016,9 +1043,7 @@ namespace ts { if (path.charCodeAt(indexAfterNodeModules) === CharacterCodes.at) { indexAfterPackageName = moveToNextDirectorySeparatorIfAvailable(path, indexAfterPackageName); } - const packageDirectory = path.slice(0, indexAfterPackageName); - const subModuleName = removeExtension(path.slice(indexAfterPackageName + 1), resolved.ext) + Extension.Dts; - return { packageDirectory, subModuleName }; + return path.slice(0, indexAfterPackageName); } function moveToNextDirectorySeparatorIfAvailable(path: string, prevSeparatorIndex: number): number { @@ -1026,19 +1051,6 @@ namespace ts { return nextSeparatorIndex === -1 ? prevSeparatorIndex : nextSeparatorIndex; } - function addExtensionAndIndex(path: string): string { - if (path === "") { - return "index.d.ts"; - } - if (endsWith(path, ".d.ts")) { - return path; - } - if (path === "index" || endsWith(path, "/index")) { - return path + ".d.ts"; - } - return path + "/index.d.ts"; - } - function loadModuleFromFileNoPackageId(extensions: Extensions, candidate: string, onlyRecordFailures: boolean, state: ModuleResolutionState): Resolved | undefined { return noPackageId(loadModuleFromFile(extensions, candidate, onlyRecordFailures, state)); } @@ -1119,61 +1131,29 @@ namespace ts { } function loadNodeModuleFromDirectory(extensions: Extensions, candidate: string, onlyRecordFailures: boolean, state: ModuleResolutionState, considerPackageJson = true) { - const packageInfo = considerPackageJson ? getPackageJsonInfo(candidate, "", onlyRecordFailures, state) : undefined; - const packageId = packageInfo && packageInfo.packageId; + const packageInfo = considerPackageJson ? getPackageJsonInfo(candidate, onlyRecordFailures, state) : undefined; const packageJsonContent = packageInfo && packageInfo.packageJsonContent; const versionPaths = packageInfo && packageInfo.versionPaths; - return withPackageId(packageId, loadNodeModuleFromDirectoryWorker(extensions, candidate, onlyRecordFailures, state, packageJsonContent, versionPaths)); + return withPackageId(packageInfo, loadNodeModuleFromDirectoryWorker(extensions, candidate, onlyRecordFailures, state, packageJsonContent, versionPaths)); } interface PackageJsonInfo { - packageJsonContent: PackageJsonPathFields | undefined; - packageId: PackageId | undefined; + packageDirectory: string; + packageJsonContent: PackageJsonPathFields; versionPaths: VersionPaths | undefined; } - function getPackageJsonInfo(packageDirectory: string, subModuleName: string, onlyRecordFailures: boolean, state: ModuleResolutionState): PackageJsonInfo | undefined { + function getPackageJsonInfo(packageDirectory: string, onlyRecordFailures: boolean, state: ModuleResolutionState): PackageJsonInfo | undefined { const { host, traceEnabled } = state; const directoryExists = !onlyRecordFailures && directoryProbablyExists(packageDirectory, host); const packageJsonPath = combinePaths(packageDirectory, "package.json"); if (directoryExists && host.fileExists(packageJsonPath)) { const packageJsonContent = readJson(packageJsonPath, host) as PackageJson; - if (subModuleName === "") { // looking up the root - need to handle types/typings/main redirects for subModuleName - const path = readPackageJsonTypesFields(packageJsonContent, packageDirectory, state); - if (typeof path === "string") { - subModuleName = addExtensionAndIndex(path.substring(packageDirectory.length + 1)); - } - else { - const jsPath = readPackageJsonMainField(packageJsonContent, packageDirectory, state); - if (typeof jsPath === "string" && jsPath.length > packageDirectory.length) { - const potentialSubModule = jsPath.substring(packageDirectory.length + 1); - subModuleName = (forEach(supportedJSExtensions, extension => - tryRemoveExtension(potentialSubModule, extension)) || potentialSubModule) + Extension.Dts; - } - else { - subModuleName = "index.d.ts"; - } - } - } - - if (!endsWith(subModuleName, Extension.Dts)) { - subModuleName = addExtensionAndIndex(subModuleName); - } - - const versionPaths = readPackageJsonTypesVersionPaths(packageJsonContent, state); - const packageId: PackageId | undefined = typeof packageJsonContent.name === "string" && typeof packageJsonContent.version === "string" - ? { name: packageJsonContent.name, subModuleName, version: packageJsonContent.version } - : undefined; if (traceEnabled) { - if (packageId) { - trace(host, Diagnostics.Found_package_json_at_0_Package_ID_is_1, packageJsonPath, packageIdToString(packageId)); - } - else { - trace(host, Diagnostics.Found_package_json_at_0, packageJsonPath); - } + trace(host, Diagnostics.Found_package_json_at_0, packageJsonPath); } - - return { packageJsonContent, packageId, versionPaths }; + const versionPaths = readPackageJsonTypesVersionPaths(packageJsonContent, state); + return { packageDirectory, packageJsonContent, versionPaths }; } else { if (directoryExists && traceEnabled) { @@ -1328,27 +1308,36 @@ namespace ts { const candidate = normalizePath(combinePaths(nodeModulesDirectory, moduleName)); // First look for a nested package.json, as in `node_modules/foo/bar/package.json`. - let packageJsonContent: PackageJsonPathFields | undefined; - let packageId: PackageId | undefined; - let versionPaths: VersionPaths | undefined; - - const packageInfo = getPackageJsonInfo(candidate, "", !nodeModulesDirectoryExists, state); + let packageInfo = getPackageJsonInfo(candidate, !nodeModulesDirectoryExists, state); if (packageInfo) { - ({ packageJsonContent, packageId, versionPaths } = packageInfo); const fromFile = loadModuleFromFile(extensions, candidate, !nodeModulesDirectoryExists, state); if (fromFile) { return noPackageId(fromFile); } - const fromDirectory = loadNodeModuleFromDirectoryWorker(extensions, candidate, !nodeModulesDirectoryExists, state, packageJsonContent, versionPaths); - return withPackageId(packageId, fromDirectory); + const fromDirectory = loadNodeModuleFromDirectoryWorker( + extensions, + candidate, + !nodeModulesDirectoryExists, + state, + packageInfo.packageJsonContent, + packageInfo.versionPaths + ); + return withPackageId(packageInfo, fromDirectory); } const loader: ResolutionKindSpecificLoader = (extensions, candidate, onlyRecordFailures, state) => { const pathAndExtension = loadModuleFromFile(extensions, candidate, onlyRecordFailures, state) || - loadNodeModuleFromDirectoryWorker(extensions, candidate, onlyRecordFailures, state, packageJsonContent, versionPaths); - return withPackageId(packageId, pathAndExtension); + loadNodeModuleFromDirectoryWorker( + extensions, + candidate, + onlyRecordFailures, + state, + packageInfo && packageInfo.packageJsonContent, + packageInfo && packageInfo.versionPaths + ); + return withPackageId(packageInfo, pathAndExtension); }; const { packageName, rest } = parsePackageName(moduleName); @@ -1356,14 +1345,13 @@ namespace ts { const packageDirectory = combinePaths(nodeModulesDirectory, packageName); // Don't use a "types" or "main" from here because we're not loading the root, but a subdirectory -- just here for the packageId and path mappings. - const packageInfo = getPackageJsonInfo(packageDirectory, rest, !nodeModulesDirectoryExists, state); - if (packageInfo) ({ packageId, versionPaths } = packageInfo); - if (versionPaths) { + packageInfo = getPackageJsonInfo(packageDirectory, !nodeModulesDirectoryExists, state); + if (packageInfo && packageInfo.versionPaths) { if (state.traceEnabled) { - trace(state.host, Diagnostics.package_json_has_a_typesVersions_entry_0_that_matches_compiler_version_1_looking_for_a_pattern_to_match_module_name_2, versionPaths.version, version, rest); + trace(state.host, Diagnostics.package_json_has_a_typesVersions_entry_0_that_matches_compiler_version_1_looking_for_a_pattern_to_match_module_name_2, packageInfo.versionPaths.version, version, rest); } const packageDirectoryExists = nodeModulesDirectoryExists && directoryProbablyExists(packageDirectory, state.host); - const fromPaths = tryLoadModuleUsingPaths(extensions, rest, packageDirectory, versionPaths.paths, loader, !packageDirectoryExists, state); + const fromPaths = tryLoadModuleUsingPaths(extensions, rest, packageDirectory, packageInfo.versionPaths.paths, loader, !packageDirectoryExists, state); if (fromPaths) { return fromPaths.value; } @@ -1499,8 +1487,8 @@ namespace ts { } /** - * LSHost may load a module from a global cache of typings. - * This is the minumum code needed to expose that functionality; the rest is in LSHost. + * A host may load a module from a global cache of typings. + * This is the minumum code needed to expose that functionality; the rest is in the host. */ /* @internal */ export function loadModuleFromGlobalCache(moduleName: string, projectName: string | undefined, compilerOptions: CompilerOptions, host: ModuleResolutionHost, globalCache: string): ResolvedModuleWithFailedLookupLocations { 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/parser.ts b/src/compiler/parser.ts index c38e4f67fe2..d9d2f761595 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -4430,14 +4430,18 @@ namespace ts { if (token() !== SyntaxKind.CloseBraceToken) { node.dotDotDotToken = parseOptionalToken(SyntaxKind.DotDotDotToken); - node.expression = parseAssignmentExpressionOrHigher(); + // Only an AssignmentExpression is valid here per the JSX spec, + // but we can unambiguously parse a comma sequence and provide + // a better error message in grammar checking. + node.expression = parseExpression(); } if (inExpressionContext) { parseExpected(SyntaxKind.CloseBraceToken); } else { - parseExpected(SyntaxKind.CloseBraceToken, /*message*/ undefined, /*shouldAdvance*/ false); - scanJsxText(); + if (parseExpected(SyntaxKind.CloseBraceToken, /*message*/ undefined, /*shouldAdvance*/ false)) { + scanJsxText(); + } } return finishNode(node); @@ -6427,6 +6431,7 @@ namespace ts { BeginningOfLine, SawAsterisk, SavingComments, + SavingBackticks, // NOTE: Only used when parsing tag comments } const enum PropertyLikeParse { @@ -6687,18 +6692,23 @@ namespace ts { case SyntaxKind.NewLineTrivia: if (state >= JSDocState.SawAsterisk) { state = JSDocState.BeginningOfLine; + // don't use pushComment here because we want to keep the margin unchanged comments.push(scanner.getTokenText()); } indent = 0; break; case SyntaxKind.AtToken: + if (state === JSDocState.SavingBackticks) { + comments.push(scanner.getTokenText()); + break; + } scanner.setTextPos(scanner.getTextPos() - 1); // falls through case SyntaxKind.EndOfFileToken: // Done break loop; case SyntaxKind.WhitespaceTrivia: - if (state === JSDocState.SavingComments) { + if (state === JSDocState.SavingComments || state === JSDocState.SavingBackticks) { pushComment(scanner.getTokenText()); } else { @@ -6720,6 +6730,15 @@ namespace ts { } pushComment(scanner.getTokenText()); break; + case SyntaxKind.BacktickToken: + if (state === JSDocState.SavingBackticks) { + state = JSDocState.SavingComments; + } + else { + state = JSDocState.SavingBackticks; + } + pushComment(scanner.getTokenText()); + break; case SyntaxKind.AsteriskToken: if (state === JSDocState.BeginningOfLine) { // leading asterisks start recording on the *next* (non-whitespace) token @@ -6730,7 +6749,9 @@ namespace ts { // record the * as a comment // falls through default: - state = JSDocState.SavingComments; // leading identifiers start recording as well + if (state !== JSDocState.SavingBackticks) { + state = JSDocState.SavingComments; // leading identifiers start recording as well + } pushComment(scanner.getTokenText()); break; } diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 2fda6ea49a5..889d24549e5 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -1395,7 +1395,7 @@ namespace ts { const filePath = newSourceFile.path; addFileToFilesByName(newSourceFile, filePath, newSourceFile.resolvedPath); // Set the file as found during node modules search if it was found that way in old progra, - if (oldProgram.isSourceFileFromExternalLibrary(oldProgram.getSourceFileByPath(filePath)!)) { + if (oldProgram.isSourceFileFromExternalLibrary(oldProgram.getSourceFileByPath(newSourceFile.resolvedPath)!)) { sourceFilesFoundSearchingNodeModules.set(filePath, true); } } @@ -1442,7 +1442,8 @@ namespace ts { }, ...(host.directoryExists ? { directoryExists: f => host.directoryExists!(f) } : {}), useCaseSensitiveFileNames: () => host.useCaseSensitiveFileNames(), - getProgramBuildInfo: () => program.getProgramBuildInfo && program.getProgramBuildInfo() + getProgramBuildInfo: () => program.getProgramBuildInfo && program.getProgramBuildInfo(), + getSourceFileFromReference: (file, ref) => program.getSourceFileFromReference(file, ref), }; } @@ -2127,7 +2128,7 @@ namespace ts { } /** This should have similar behavior to 'processSourceFile' without diagnostics or mutation. */ - function getSourceFileFromReference(referencingFile: SourceFile, ref: FileReference): SourceFile | undefined { + function getSourceFileFromReference(referencingFile: SourceFile | UnparsedSource, ref: FileReference): SourceFile | undefined { return getSourceFileFromReferenceWorker(resolveTripleslashReference(ref.fileName, referencingFile.fileName), fileName => filesByName.get(toPath(fileName)) || undefined); } @@ -2232,7 +2233,10 @@ namespace ts { if (isRedirect) { inputName = getProjectReferenceRedirect(fileName) || fileName; } - if (getNormalizedAbsolutePath(checkedName, currentDirectory) !== getNormalizedAbsolutePath(inputName, currentDirectory)) { + // Check if it differs only in drive letters its ok to ignore that error: + const checkedAbsolutePath = getNormalizedAbsolutePathWithoutRoot(checkedName, currentDirectory); + const inputAbsolutePath = getNormalizedAbsolutePathWithoutRoot(inputName, currentDirectory); + if (checkedAbsolutePath !== inputAbsolutePath) { reportFileNamesDifferOnlyInCasingError(inputName, checkedName, refFile, refPos, refEnd); } } @@ -2770,7 +2774,7 @@ namespace ts { // Ignore file that is not emitted if (!sourceFileMayBeEmitted(file, options, isSourceFileFromExternalLibrary, getResolvedProjectReferenceToRedirect)) continue; if (rootPaths.indexOf(file.path) === -1) { - programDiagnostics.add(createCompilerDiagnostic(Diagnostics.File_0_is_not_in_project_file_list_Projects_must_list_all_files_or_use_an_include_pattern, file.fileName)); + programDiagnostics.add(createCompilerDiagnostic(Diagnostics.File_0_is_not_listed_within_the_file_list_of_project_1_Projects_must_list_all_files_or_use_an_include_pattern, file.fileName, options.configFilePath || "")); } } } @@ -2855,10 +2859,10 @@ namespace ts { createDiagnosticForOptionName(Diagnostics.Option_isolatedModules_can_only_be_used_when_either_option_module_is_provided_or_option_target_is_ES2015_or_higher, "isolatedModules", "target"); } - const firstNonExternalModuleSourceFile = find(files, f => !isExternalModule(f) && !f.isDeclarationFile && f.scriptKind !== ScriptKind.JSON); + const firstNonExternalModuleSourceFile = find(files, f => !isExternalModule(f) && !isSourceFileJS(f) && !f.isDeclarationFile && f.scriptKind !== ScriptKind.JSON); if (firstNonExternalModuleSourceFile) { const span = getErrorSpanForNode(firstNonExternalModuleSourceFile, firstNonExternalModuleSourceFile); - programDiagnostics.add(createFileDiagnostic(firstNonExternalModuleSourceFile, span.start, span.length, Diagnostics.Cannot_compile_namespaces_when_the_isolatedModules_flag_is_provided)); + programDiagnostics.add(createFileDiagnostic(firstNonExternalModuleSourceFile, span.start, span.length, Diagnostics.All_files_must_be_modules_when_the_isolatedModules_flag_is_provided)); } } else if (firstNonAmbientExternalModuleSourceFile && languageVersion < ScriptTarget.ES2015 && options.module === ModuleKind.None) { diff --git a/src/compiler/resolutionCache.ts b/src/compiler/resolutionCache.ts index b82a5a78d71..13e6b9f280e 100644 --- a/src/compiler/resolutionCache.ts +++ b/src/compiler/resolutionCache.ts @@ -51,9 +51,11 @@ namespace ts { getCachedDirectoryStructureHost(): CachedDirectoryStructureHost | undefined; projectName?: string; getGlobalCache?(): string | undefined; + globalCacheResolutionModuleName?(externalModuleName: string): string; writeLog(s: string): void; maxNumberOfFilesToIterateForInvalidation?: number; getCurrentProgram(): Program | undefined; + fileIsOpen(filePath: Path): boolean; } interface DirectoryWatchesOfFailedLookup { @@ -75,6 +77,41 @@ namespace ts { return some(ignoredPaths, searchPath => stringContains(path, searchPath)); } + /** + * Filter out paths like + * "/", "/user", "/user/username", "/user/username/folderAtRoot", + * "c:/", "c:/users", "c:/users/username", "c:/users/username/folderAtRoot", "c:/folderAtRoot" + * @param dirPath + */ + export function canWatchDirectory(dirPath: Path) { + const rootLength = getRootLength(dirPath); + if (dirPath.length === rootLength) { + // Ignore "/", "c:/" + return false; + } + + const nextDirectorySeparator = dirPath.indexOf(directorySeparator, rootLength); + if (nextDirectorySeparator === -1) { + // ignore "/user", "c:/users" or "c:/folderAtRoot" + return false; + } + + if (dirPath.charCodeAt(0) !== CharacterCodes.slash && + dirPath.substr(rootLength, nextDirectorySeparator).search(/users/i) === -1) { + // Paths like c:/folderAtRoot/subFolder are allowed + return true; + } + + for (let searchIndex = nextDirectorySeparator + 1, searchLevels = 2; searchLevels > 0; searchLevels--) { + searchIndex = dirPath.indexOf(directorySeparator, searchIndex) + 1; + if (searchIndex === 0) { + // Folder isnt at expected minimun levels + return false; + } + } + return true; + } + export const maxNumberOfFilesToIterateForInvalidation = 256; type GetResolutionWithResolvedFileName = @@ -234,7 +271,12 @@ namespace ts { if (globalCache !== undefined && !isExternalModuleNameRelative(moduleName) && !(primaryResult.resolvedModule && extensionIsTS(primaryResult.resolvedModule.extension))) { // create different collection of failed lookup locations for second pass // if it will fail and we've already found something during the first pass - we don't want to pollute its results - const { resolvedModule, failedLookupLocations } = loadModuleFromGlobalCache(moduleName, resolutionHost.projectName, compilerOptions, host, globalCache); + const { resolvedModule, failedLookupLocations } = loadModuleFromGlobalCache( + Debug.assertDefined(resolutionHost.globalCacheResolutionModuleName)(moduleName), + resolutionHost.projectName, + compilerOptions, + host, + globalCache); if (resolvedModule) { return { resolvedModule, failedLookupLocations: addRange(primaryResult.failedLookupLocations as string[], failedLookupLocations) }; } @@ -372,41 +414,6 @@ namespace ts { return endsWith(dirPath, "/node_modules/@types"); } - /** - * Filter out paths like - * "/", "/user", "/user/username", "/user/username/folderAtRoot", - * "c:/", "c:/users", "c:/users/username", "c:/users/username/folderAtRoot", "c:/folderAtRoot" - * @param dirPath - */ - function canWatchDirectory(dirPath: Path) { - const rootLength = getRootLength(dirPath); - if (dirPath.length === rootLength) { - // Ignore "/", "c:/" - return false; - } - - const nextDirectorySeparator = dirPath.indexOf(directorySeparator, rootLength); - if (nextDirectorySeparator === -1) { - // ignore "/user", "c:/users" or "c:/folderAtRoot" - return false; - } - - if (dirPath.charCodeAt(0) !== CharacterCodes.slash && - dirPath.substr(rootLength, nextDirectorySeparator).search(/users/i) === -1) { - // Paths like c:/folderAtRoot/subFolder are allowed - return true; - } - - for (let searchIndex = nextDirectorySeparator + 1, searchLevels = 2; searchLevels > 0; searchLevels--) { - searchIndex = dirPath.indexOf(directorySeparator, searchIndex) + 1; - if (searchIndex === 0) { - // Folder isnt at expected minimun levels - return false; - } - } - return true; - } - function getDirectoryToWatchFailedLookupLocation(failedLookupLocation: string, failedLookupLocationPath: Path): DirectoryOfFailedLookupWatch | undefined { if (isInDirectoryPath(rootPath, failedLookupLocationPath)) { // Ensure failed look up is normalized path @@ -698,6 +705,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/sys.ts b/src/compiler/sys.ts index b1923a73a27..5abc19cdbc4 100644 --- a/src/compiler/sys.ts +++ b/src/compiler/sys.ts @@ -330,7 +330,7 @@ namespace ts { } /*@internal*/ - export const ignoredPaths = ["/node_modules/.", "/.git"]; + export const ignoredPaths = ["/node_modules/.", "/.git", "/.#"]; /*@internal*/ export interface RecursiveDirectoryWatcherHost { 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..f213f679b9c --- /dev/null +++ b/src/compiler/transformers/classFields.ts @@ -0,0 +1,511 @@ +/*@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); + + case SyntaxKind.SemicolonClassElement: + return node; + + default: + return visitor(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, + /*decorators*/ undefined, + node.modifiers, + node.name, + /*typeParameters*/ undefined, + visitNodes(node.heritageClauses, visitor, isHeritageClause), + 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, + /*typeParameters*/ undefined, + 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; + // } + // + if (constructor && constructor.body) { + let parameterPropertyDeclarationCount = 0; + for (let i = indexOfFirstStatement; i < constructor.body.statements.length; i++) { + if (isParameterPropertyDeclaration(getOriginalNode(constructor.body.statements[i]))) { + parameterPropertyDeclarationCount++; + } + else { + break; + } + } + if (parameterPropertyDeclarationCount > 0) { + addRange(statements, visitNodes(constructor.body.statements, visitor, isStatement, indexOfFirstStatement, parameterPropertyDeclarationCount)); + indexOfFirstStatement += parameterPropertyDeclarationCount; + } + } + 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/declarations.ts b/src/compiler/transformers/declarations.ts index 20aac7d4936..bb939f7d4f1 100644 --- a/src/compiler/transformers/declarations.ts +++ b/src/compiler/transformers/declarations.ts @@ -190,6 +190,10 @@ namespace ts { } } + function createEmptyExports() { + return createExportDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, createNamedExports([]), /*moduleSpecifier*/ undefined); + } + function transformRoot(node: Bundle): Bundle; function transformRoot(node: SourceFile): SourceFile; function transformRoot(node: SourceFile | Bundle): SourceFile | Bundle; @@ -277,7 +281,7 @@ namespace ts { refs.forEach(referenceVisitor); const emittedImports = filter(combinedStatements, isAnyImportSyntax); if (isExternalModule(node) && (!resultHasExternalModuleIndicator || (needsScopeFixMarker && !resultHasScopeMarker))) { - combinedStatements = setTextRange(createNodeArray([...combinedStatements, createExportDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, createNamedExports([]), /*moduleSpecifier*/ undefined)]), combinedStatements); + combinedStatements = setTextRange(createNodeArray([...combinedStatements, createEmptyExports()]), combinedStatements); } const updated = updateSourceFileNode(node, combinedStatements, /*isDeclarationFile*/ true, references, getFileReferencesForUsedTypeReferences(), node.hasNoDefaultLib, getLibReferences()); updated.exportedModulesFromDeclarationEmit = exportedModulesFromDeclarationEmit; @@ -348,7 +352,7 @@ namespace ts { function collectReferences(sourceFile: SourceFile | UnparsedSource, ret: Map) { if (noResolve || (!isUnparsedSource(sourceFile) && isSourceFileJS(sourceFile))) return ret; forEach(sourceFile.referencedFiles, f => { - const elem = tryResolveScriptReference(host, sourceFile, f); + const elem = host.getSourceFileFromReference(sourceFile, f); if (elem) { ret.set("" + getOriginalNodeId(elem), elem); } @@ -670,7 +674,7 @@ namespace ts { } const priorNeedsDeclare = needsDeclare; needsDeclare = i.parent && isSourceFile(i.parent) && !(isExternalModule(i.parent) && isBundledEmit); - const result = transformTopLevelDeclaration(i, /*privateDeclaration*/ true); + const result = transformTopLevelDeclaration(i); needsDeclare = priorNeedsDeclare; lateStatementReplacementMap.set("" + getOriginalNodeId(i), result); } @@ -685,12 +689,12 @@ namespace ts { if (lateStatementReplacementMap.has(key)) { const result = lateStatementReplacementMap.get(key); lateStatementReplacementMap.delete(key); - if (result && isSourceFile(statement.parent)) { + if (result) { if (isArray(result) ? some(result, needsScopeMarker) : needsScopeMarker(result)) { // Top-level declarations in .d.ts files are always considered exported even without a modifier unless there's an export assignment or specifier needsScopeFixMarker = true; } - if (isArray(result) ? some(result, isExternalModuleIndicator) : isExternalModuleIndicator(result)) { + if (isSourceFile(statement.parent) && (isArray(result) ? some(result, isExternalModuleIndicator) : isExternalModuleIndicator(result))) { resultHasExternalModuleIndicator = true; } } @@ -939,8 +943,8 @@ namespace ts { case SyntaxKind.ExportDeclaration: { if (isSourceFile(input.parent)) { resultHasExternalModuleIndicator = true; - resultHasScopeMarker = true; } + resultHasScopeMarker = true; // Always visible if the parent node isn't dropped for being not visible // Rewrite external module names if necessary return updateExportDeclaration(input, /*decorators*/ undefined, input.modifiers, input.exportClause, rewriteModuleSpecifier(input, input.moduleSpecifier)); @@ -949,8 +953,8 @@ namespace ts { // Always visible if the parent node isn't dropped for being not visible if (isSourceFile(input.parent)) { resultHasExternalModuleIndicator = true; - resultHasScopeMarker = true; } + resultHasScopeMarker = true; if (input.expression.kind === SyntaxKind.Identifier) { return input; } @@ -973,7 +977,19 @@ namespace ts { return input; } - function transformTopLevelDeclaration(input: LateVisibilityPaintedStatement, isPrivate?: boolean) { + function stripExportModifiers(statement: Statement): Statement { + if (isImportEqualsDeclaration(statement) || hasModifier(statement, ModifierFlags.Default)) { + // `export import` statements should remain as-is, as imports are _not_ implicitly exported in an ambient namespace + // Likewise, `export default` classes and the like and just be `default`, so we preserve their `export` modifiers, too + return statement; + } + const clone = getMutableClone(statement); + const modifiers = createModifiersFromModifierFlags(getModifierFlags(statement) & (ModifierFlags.All ^ ModifierFlags.Export)); + clone.modifiers = modifiers.length ? createNodeArray(modifiers) : undefined; + return clone; + } + + function transformTopLevelDeclaration(input: LateVisibilityPaintedStatement) { if (shouldStripInternal(input)) return; switch (input.kind) { case SyntaxKind.ImportEqualsDeclaration: { @@ -1006,7 +1022,7 @@ namespace ts { return cleanup(updateTypeAliasDeclaration( input, /*decorators*/ undefined, - ensureModifiers(input, isPrivate), + ensureModifiers(input), input.name, visitNodes(input.typeParameters, visitDeclarationSubtree, isTypeParameterDeclaration), visitNode(input.type, visitDeclarationSubtree, isTypeNode) @@ -1015,7 +1031,7 @@ namespace ts { return cleanup(updateInterfaceDeclaration( input, /*decorators*/ undefined, - ensureModifiers(input, isPrivate), + ensureModifiers(input), input.name, ensureTypeParams(input, input.typeParameters), transformHeritageClauses(input.heritageClauses), @@ -1027,7 +1043,7 @@ namespace ts { const clean = cleanup(updateFunctionDeclaration( input, /*decorators*/ undefined, - ensureModifiers(input, isPrivate), + ensureModifiers(input), /*asteriskToken*/ undefined, input.name, ensureTypeParams(input, input.typeParameters), @@ -1036,19 +1052,25 @@ namespace ts { /*body*/ undefined )); if (clean && resolver.isExpandoFunctionDeclaration(input)) { - const declarations = mapDefined(resolver.getPropertiesOfContainerFunction(input), p => { + const props = resolver.getPropertiesOfContainerFunction(input); + const fakespace = createModuleDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, clean.name || createIdentifier("_default"), createModuleBlock([]), NodeFlags.Namespace); + fakespace.flags ^= NodeFlags.Synthesized; // unset synthesized so it is usable as an enclosing declaration + fakespace.parent = enclosingDeclaration as SourceFile | NamespaceDeclaration; + fakespace.locals = createSymbolTable(props); + fakespace.symbol = props[0].parent!; + const declarations = mapDefined(props, p => { if (!isPropertyAccessExpression(p.valueDeclaration)) { return undefined; } getSymbolAccessibilityDiagnostic = createGetSymbolAccessibilityDiagnosticForNode(p.valueDeclaration); - const type = resolver.createTypeOfDeclaration(p.valueDeclaration, enclosingDeclaration, declarationEmitNodeBuilderFlags, symbolTracker); + const type = resolver.createTypeOfDeclaration(p.valueDeclaration, fakespace, declarationEmitNodeBuilderFlags, symbolTracker); getSymbolAccessibilityDiagnostic = oldDiag; const varDecl = createVariableDeclaration(unescapeLeadingUnderscores(p.escapedName), type, /*initializer*/ undefined); return createVariableStatement(/*modifiers*/ undefined, createVariableDeclarationList([varDecl])); }); - const namespaceDecl = createModuleDeclaration(/*decorators*/ undefined, ensureModifiers(input, isPrivate), input.name!, createModuleBlock(declarations), NodeFlags.Namespace); + const namespaceDecl = createModuleDeclaration(/*decorators*/ undefined, ensureModifiers(input), input.name!, createModuleBlock(declarations), NodeFlags.Namespace); - if (!hasModifier(clean, ModifierFlags.ExportDefault)) { + if (!hasModifier(clean, ModifierFlags.Default)) { return [clean, namespaceDecl]; } @@ -1080,7 +1102,9 @@ namespace ts { namespaceDecl.name ); - resultHasExternalModuleIndicator = true; + if (isSourceFile(input.parent)) { + resultHasExternalModuleIndicator = true; + } resultHasScopeMarker = true; return [cleanDeclaration, namespaceDeclaration, exportDefaultDeclaration]; @@ -1093,10 +1117,32 @@ namespace ts { needsDeclare = false; const inner = input.body; if (inner && inner.kind === SyntaxKind.ModuleBlock) { + const oldNeedsScopeFix = needsScopeFixMarker; + const oldHasScopeFix = resultHasScopeMarker; + resultHasScopeMarker = false; + needsScopeFixMarker = false; const statements = visitNodes(inner.statements, visitDeclarationStatements); - const body = updateModuleBlock(inner, transformAndReplaceLatePaintedStatements(statements)); + let lateStatements = transformAndReplaceLatePaintedStatements(statements); + if (input.flags & NodeFlags.Ambient) { + needsScopeFixMarker = false; // If it was `declare`'d everything is implicitly exported already, ignore late printed "privates" + } + // With the final list of statements, there are 3 possibilities: + // 1. There's an export assignment or export declaration in the namespace - do nothing + // 2. Everything is exported and there are no export assignments or export declarations - strip all export modifiers + // 3. Some things are exported, some are not, and there's no marker - add an empty marker + if (!isGlobalScopeAugmentation(input) && !hasScopeMarker(lateStatements) && !resultHasScopeMarker) { + if (needsScopeFixMarker) { + lateStatements = createNodeArray([...lateStatements, createEmptyExports()]); + } + else { + lateStatements = visitNodes(lateStatements, stripExportModifiers); + } + } + const body = updateModuleBlock(inner, lateStatements); needsDeclare = previousNeedsDeclare; - const mods = ensureModifiers(input, isPrivate); + needsScopeFixMarker = oldNeedsScopeFix; + resultHasScopeMarker = oldHasScopeFix; + const mods = ensureModifiers(input); return cleanup(updateModuleDeclaration( input, /*decorators*/ undefined, @@ -1107,7 +1153,7 @@ namespace ts { } else { needsDeclare = previousNeedsDeclare; - const mods = ensureModifiers(input, isPrivate); + const mods = ensureModifiers(input); needsDeclare = false; visitNode(inner, visitDeclarationStatements); // eagerly transform nested namespaces (the nesting doesn't need any elision or painting done) @@ -1124,7 +1170,7 @@ namespace ts { } } case SyntaxKind.ClassDeclaration: { - const modifiers = createNodeArray(ensureModifiers(input, isPrivate)); + const modifiers = createNodeArray(ensureModifiers(input)); const typeParameters = ensureTypeParams(input, input.typeParameters); const ctor = getFirstConstructorWithBody(input); let parameterProperties: ReadonlyArray | undefined; @@ -1218,10 +1264,10 @@ namespace ts { } } case SyntaxKind.VariableStatement: { - return cleanup(transformVariableStatement(input, isPrivate)); + return cleanup(transformVariableStatement(input)); } case SyntaxKind.EnumDeclaration: { - return cleanup(updateEnumDeclaration(input, /*decorators*/ undefined, createNodeArray(ensureModifiers(input, isPrivate)), input.name, createNodeArray(mapDefined(input.members, m => { + return cleanup(updateEnumDeclaration(input, /*decorators*/ undefined, createNodeArray(ensureModifiers(input)), input.name, createNodeArray(mapDefined(input.members, m => { if (shouldStripInternal(m)) return; // Rewrite enum values to their constants, if available const constValue = resolver.getConstantValue(m); @@ -1249,11 +1295,11 @@ namespace ts { } } - function transformVariableStatement(input: VariableStatement, privateDeclaration?: boolean) { + function transformVariableStatement(input: VariableStatement) { if (!forEach(input.declarationList.declarations, getBindingNameVisible)) return; const nodes = visitNodes(input.declarationList.declarations, visitDeclarationSubtree); if (!length(nodes)) return; - return updateVariableStatement(input, createNodeArray(ensureModifiers(input, privateDeclaration)), updateVariableDeclarationList(input.declarationList, nodes)); + return updateVariableStatement(input, createNodeArray(ensureModifiers(input)), updateVariableDeclarationList(input.declarationList, nodes)); } function recreateBindingPattern(d: BindingPattern): VariableDeclaration[] { @@ -1300,28 +1346,25 @@ namespace ts { return isExportAssignment(node) || isExportDeclaration(node); } - function hasScopeMarker(node: Node) { - if (isModuleBlock(node)) { - return some(node.statements, isScopeMarker); - } - return false; + function hasScopeMarker(statements: ReadonlyArray) { + return some(statements, isScopeMarker); } - function ensureModifiers(node: Node, privateDeclaration?: boolean): ReadonlyArray | undefined { + function ensureModifiers(node: Node): ReadonlyArray | undefined { const currentFlags = getModifierFlags(node); - const newFlags = ensureModifierFlags(node, privateDeclaration); + const newFlags = ensureModifierFlags(node); if (currentFlags === newFlags) { return node.modifiers; } return createModifiersFromModifierFlags(newFlags); } - function ensureModifierFlags(node: Node, privateDeclaration?: boolean): ModifierFlags { + function ensureModifierFlags(node: Node): ModifierFlags { let mask = ModifierFlags.All ^ (ModifierFlags.Public | ModifierFlags.Async); // No async modifiers in declaration files let additions = (needsDeclare && !isAlwaysType(node)) ? ModifierFlags.Ambient : ModifierFlags.None; const parentIsFile = node.parent.kind === SyntaxKind.SourceFile; if (!parentIsFile || (isBundledEmit && parentIsFile && isExternalModule(node.parent as SourceFile))) { - mask ^= ((privateDeclaration || (isBundledEmit && parentIsFile) || hasScopeMarker(node.parent) ? 0 : ModifierFlags.Export) | ModifierFlags.Ambient); + mask ^= ModifierFlags.Ambient; additions = ModifierFlags.None; } return maskModifierFlags(node, mask, additions); diff --git a/src/compiler/transformers/es2015.ts b/src/compiler/transformers/es2015.ts index cdeae5dbf54..7623d19dd25 100644 --- a/src/compiler/transformers/es2015.ts +++ b/src/compiler/transformers/es2015.ts @@ -145,7 +145,7 @@ namespace ts { loopOutParameters: LoopOutParameter[]; } - type LoopConverter = (node: IterationStatement, outermostLabeledStatement: LabeledStatement | undefined, convertedLoopBodyStatements: Statement[] | undefined) => Statement; + type LoopConverter = (node: IterationStatement, outermostLabeledStatement: LabeledStatement | undefined, convertedLoopBodyStatements: Statement[] | undefined, ancestorFacts: HierarchyFacts) => Statement; // Facts we track as we traverse the tree const enum HierarchyFacts { @@ -163,11 +163,12 @@ namespace ts { ExportedVariableStatement = 1 << 5, // Enclosed in an exported variable statement in the current scope TopLevel = 1 << 6, // Enclosing block-scoped container is a top-level container Block = 1 << 7, // Enclosing block-scoped container is a Block - IterationStatement = 1 << 8, // Enclosed in an IterationStatement + IterationStatement = 1 << 8, // Immediately enclosed in an IterationStatement IterationStatementBlock = 1 << 9, // Enclosing Block is enclosed in an IterationStatement - ForStatement = 1 << 10, // Enclosing block-scoped container is a ForStatement - ForInOrForOfStatement = 1 << 11, // Enclosing block-scoped container is a ForInStatement or ForOfStatement - ConstructorWithCapturedSuper = 1 << 12, // Enclosed in a constructor that captures 'this' for use with 'super' + IterationContainer = 1 << 10, // Enclosed in an outer IterationStatement + ForStatement = 1 << 11, // Enclosing block-scoped container is a ForStatement + ForInOrForOfStatement = 1 << 12, // Enclosing block-scoped container is a ForInStatement or ForOfStatement + ConstructorWithCapturedSuper = 1 << 13, // Enclosed in a constructor that captures 'this' for use with 'super' // NOTE: do not add more ancestor flags without also updating AncestorFactsMask below. // NOTE: when adding a new ancestor flag, be sure to update the subtree flags below. @@ -184,11 +185,11 @@ namespace ts { // A source file is a top-level block scope. SourceFileIncludes = TopLevel, - SourceFileExcludes = BlockScopeExcludes & ~TopLevel, + SourceFileExcludes = BlockScopeExcludes & ~TopLevel | IterationContainer, // Functions, methods, and accessors are both new lexical scopes and new block scopes. FunctionIncludes = Function | TopLevel, - FunctionExcludes = BlockScopeExcludes & ~TopLevel | ArrowFunction | AsyncFunctionBody | CapturesThis | NonStaticClassElement | ConstructorWithCapturedSuper, + FunctionExcludes = BlockScopeExcludes & ~TopLevel | ArrowFunction | AsyncFunctionBody | CapturesThis | NonStaticClassElement | ConstructorWithCapturedSuper | IterationContainer, AsyncFunctionBodyIncludes = FunctionIncludes | AsyncFunctionBody, AsyncFunctionBodyExcludes = FunctionExcludes & ~NonStaticClassElement, @@ -205,16 +206,16 @@ namespace ts { // 'do' and 'while' statements are not block scopes. We track that the subtree is contained // within an IterationStatement to indicate whether the embedded statement is an // IterationStatementBlock. - DoOrWhileStatementIncludes = IterationStatement, + DoOrWhileStatementIncludes = IterationStatement | IterationContainer, DoOrWhileStatementExcludes = None, // 'for' statements are new block scopes and have special handling for 'let' declarations. - ForStatementIncludes = IterationStatement | ForStatement, + ForStatementIncludes = IterationStatement | ForStatement | IterationContainer, ForStatementExcludes = BlockScopeExcludes & ~ForStatement, // 'for-in' and 'for-of' statements are new block scopes and have special handling for // 'let' declarations. - ForInOrForOfStatementIncludes = IterationStatement | ForInOrForOfStatement, + ForInOrForOfStatementIncludes = IterationStatement | ForInOrForOfStatement | IterationContainer, ForInOrForOfStatementExcludes = BlockScopeExcludes & ~ForInOrForOfStatement, // Blocks (other than function bodies) are new block scopes. @@ -228,8 +229,8 @@ namespace ts { // Subtree facts // - NewTarget = 1 << 13, // Contains a 'new.target' meta-property - CapturedLexicalThis = 1 << 14, // Contains a lexical `this` reference captured by an arrow function. + NewTarget = 1 << 14, // Contains a 'new.target' meta-property + CapturedLexicalThis = 1 << 15, // Contains a lexical `this` reference captured by an arrow function. // // Subtree masks @@ -1566,7 +1567,7 @@ namespace ts { break; default: - Debug.failBadSyntaxKind(node); + Debug.failBadSyntaxKind(member, currentSourceFile && currentSourceFile.fileName); break; } } @@ -2227,7 +2228,7 @@ namespace ts { function visitIterationStatementWithFacts(excludeFacts: HierarchyFacts, includeFacts: HierarchyFacts, node: IterationStatement, outermostLabeledStatement: LabeledStatement | undefined, convert?: LoopConverter) { const ancestorFacts = enterSubtree(excludeFacts, includeFacts); - const updated = convertIterationStatementBodyIfNecessary(node, outermostLabeledStatement, convert); + const updated = convertIterationStatementBodyIfNecessary(node, outermostLabeledStatement, ancestorFacts, convert); exitSubtree(ancestorFacts, HierarchyFacts.None, HierarchyFacts.None); return updated; } @@ -2434,7 +2435,7 @@ namespace ts { return restoreEnclosingLabel(forStatement, outermostLabeledStatement, convertedLoopState && resetLabel); } - function convertForOfStatementForIterable(node: ForOfStatement, outermostLabeledStatement: LabeledStatement, convertedLoopBodyStatements: Statement[]): Statement { + function convertForOfStatementForIterable(node: ForOfStatement, outermostLabeledStatement: LabeledStatement, convertedLoopBodyStatements: Statement[], ancestorFacts: HierarchyFacts): Statement { const expression = visitNode(node.expression, visitor, isExpression); const iterator = isIdentifier(expression) ? getGeneratedNameForNode(expression) : createTempVariable(/*recordTempVariable*/ undefined); const result = isIdentifier(expression) ? getGeneratedNameForNode(iterator) : createTempVariable(/*recordTempVariable*/ undefined); @@ -2447,13 +2448,18 @@ namespace ts { hoistVariableDeclaration(errorRecord); hoistVariableDeclaration(returnMethod); + // if we are enclosed in an outer loop ensure we reset 'errorRecord' per each iteration + const initializer = ancestorFacts & HierarchyFacts.IterationContainer + ? inlineExpressions([createAssignment(errorRecord, createVoidZero()), values]) + : values; + const forStatement = setEmitFlags( setTextRange( createFor( /*initializer*/ setEmitFlags( setTextRange( createVariableDeclarationList([ - setTextRange(createVariableDeclaration(iterator, /*type*/ undefined, values), node.expression), + setTextRange(createVariableDeclaration(iterator, /*type*/ undefined, initializer), node.expression), createVariableDeclaration(result, /*type*/ undefined, next) ]), node.expression @@ -2665,7 +2671,7 @@ namespace ts { } } - function convertIterationStatementBodyIfNecessary(node: IterationStatement, outermostLabeledStatement: LabeledStatement | undefined, convert?: LoopConverter): VisitResult { + function convertIterationStatementBodyIfNecessary(node: IterationStatement, outermostLabeledStatement: LabeledStatement | undefined, ancestorFacts: HierarchyFacts, convert?: LoopConverter): VisitResult { if (!shouldConvertIterationStatement(node)) { let saveAllowedNonLabeledJumps: Jump | undefined; if (convertedLoopState) { @@ -2676,7 +2682,7 @@ namespace ts { } const result = convert - ? convert(node, outermostLabeledStatement, /*convertedLoopBodyStatements*/ undefined) + ? convert(node, outermostLabeledStatement, /*convertedLoopBodyStatements*/ undefined, ancestorFacts) : restoreEnclosingLabel(visitEachChild(node, visitor, context), outermostLabeledStatement, convertedLoopState && resetLabel); if (convertedLoopState) { @@ -2708,7 +2714,7 @@ namespace ts { let loop: Statement; if (bodyFunction) { if (convert) { - loop = convert(node, outermostLabeledStatement, bodyFunction.part); + loop = convert(node, outermostLabeledStatement, bodyFunction.part, ancestorFacts); } else { const clone = convertIterationStatementCore(node, initializerFunction, createBlock(bodyFunction.part, /*multiLine*/ true)); @@ -3813,8 +3819,11 @@ namespace ts { // [source] // [a, ...b, c] // + // [output (downlevelIteration)] + // __spread([a], b, [c]) + // // [output] - // [a].concat(b, [c]) + // __spreadArrays([a], b, [c]) // Map spans of spread expressions into their expressions and spans of other // expressions into an array literal. @@ -3828,10 +3837,7 @@ namespace ts { if (compilerOptions.downlevelIteration) { if (segments.length === 1) { const firstSegment = segments[0]; - if (isCallExpression(firstSegment) - && isIdentifier(firstSegment.expression) - && (getEmitFlags(firstSegment.expression) & EmitFlags.HelperName) - && firstSegment.expression.escapedText === "___spread") { + if (isCallToHelper(firstSegment, "___spread" as __String)) { return segments[0]; } } @@ -3840,17 +3846,33 @@ namespace ts { } else { if (segments.length === 1) { - const firstElement = elements[0]; - return needsUniqueCopy && isSpreadElement(firstElement) && firstElement.expression.kind !== SyntaxKind.ArrayLiteralExpression - ? createArraySlice(segments[0]) - : segments[0]; + const firstSegment = segments[0]; + if (!needsUniqueCopy + || isPackedArrayLiteral(firstSegment) + || isCallToHelper(firstSegment, "___spreadArrays" as __String)) { + return segments[0]; + } } - // Rewrite using the pattern .concat(, , ...) - return createArrayConcat(segments.shift()!, segments); + return createSpreadArraysHelper(context, segments); } } + function isPackedElement(node: Expression) { + return !isOmittedExpression(node); + } + + function isPackedArrayLiteral(node: Expression) { + return isArrayLiteralExpression(node) && every(node.elements, isPackedElement); + } + + function isCallToHelper(firstSegment: Expression, helperName: __String) { + return isCallExpression(firstSegment) + && isIdentifier(firstSegment.expression) + && (getEmitFlags(firstSegment.expression) & EmitFlags.HelperName) + && firstSegment.expression.escapedText === helperName; + } + function partitionSpread(node: Expression) { return isSpreadElement(node) ? visitSpanOfSpreads diff --git a/src/compiler/transformers/es2018.ts b/src/compiler/transformers/es2018.ts index 504870b4ff7..89c43dcf599 100644 --- a/src/compiler/transformers/es2018.ts +++ b/src/compiler/transformers/es2018.ts @@ -77,6 +77,8 @@ namespace ts { return visitObjectLiteralExpression(node as ObjectLiteralExpression); case SyntaxKind.BinaryExpression: return visitBinaryExpression(node as BinaryExpression, noDestructuringValue); + case SyntaxKind.CatchClause: + return visitCatchClause(node as CatchClause); case SyntaxKind.VariableDeclaration: return visitVariableDeclaration(node as VariableDeclaration); case SyntaxKind.ForOfStatement: @@ -272,6 +274,28 @@ namespace ts { return visitEachChild(node, visitor, context); } + function visitCatchClause(node: CatchClause) { + if (node.variableDeclaration && + isBindingPattern(node.variableDeclaration.name) && + node.variableDeclaration.name.transformFlags & TransformFlags.ContainsObjectRestOrSpread) { + const name = getGeneratedNameForNode(node.variableDeclaration.name); + const updatedDecl = updateVariableDeclaration(node.variableDeclaration, node.variableDeclaration.name, /*type*/ undefined, name); + const visitedBindings = flattenDestructuringBinding(updatedDecl, visitor, context, FlattenLevel.ObjectRest); + let block = visitNode(node.block, visitor, isBlock); + if (some(visitedBindings)) { + block = updateBlock(block, [ + createVariableStatement(/*modifiers*/ undefined, visitedBindings), + ...block.statements, + ]); + } + return updateCatchClause( + node, + updateVariableDeclaration(node.variableDeclaration, name, /*type*/ undefined, /*initializer*/ undefined), + block); + } + return visitEachChild(node, visitor, context); + } + /** * Visits a VariableDeclaration node with a binding pattern. * diff --git a/src/compiler/transformers/ts.ts b/src/compiler/transformers/ts.ts index a70fb820e79..db4c1459e5a 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; } @@ -315,12 +312,12 @@ namespace ts { function classElementVisitorWorker(node: Node): VisitResult { switch (node.kind) { case SyntaxKind.Constructor: - // TypeScript constructors are transformed in `visitClassDeclaration`. - // We elide them here as `visitorWorker` checks transform flags, which could - // erronously include an ES6 constructor without TypeScript syntax. - return undefined; + return visitConstructor(node as ConstructorDeclaration); 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 +434,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 +445,6 @@ namespace ts { // - decorators // - optional `implements` heritage clause // - parameter property assignments in the constructor - // - property declarations // - index signatures // - method overload signatures return visitClassExpression(node); @@ -612,9 +607,6 @@ namespace ts { return visitEachChild(node, visitor, context); } - const savedPendingExpressions = pendingExpressions; - pendingExpressions = undefined; - const staticProperties = getInitializedProperties(node, /*isStatic*/ true); const facts = getClassFacts(node, staticProperties); @@ -624,25 +616,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 +723,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 @@ -755,6 +733,7 @@ namespace ts { emitFlags |= EmitFlags.NoTrailingSourceMap; } + aggregateTransformFlags(classDeclaration); setTextRange(classDeclaration, node); setOriginalNode(classDeclaration, node); setEmitFlags(classDeclaration, emitFlags); @@ -765,7 +744,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,8 +839,9 @@ 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); + aggregateTransformFlags(classExpression); setOriginalNode(classExpression, node); setTextRange(classExpression, location); @@ -888,49 +868,18 @@ 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 classExpression = createClassExpression( /*modifiers*/ undefined, node.name, /*typeParameters*/ undefined, - heritageClauses, - members + visitNodes(node.heritageClauses, visitor, isHeritageClause), + transformClassMembers(node) ); + aggregateTransformFlags(classExpression); 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,344 +887,31 @@ 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); + const constructor = getFirstConstructorWithBody(node); + const parametersWithPropertyAssignments = constructor && + filter(constructor.parameters, isParameterPropertyDeclaration); + + if (parametersWithPropertyAssignments) { + for (const parameter of parametersWithPropertyAssignments) { + if (isIdentifier(parameter.name)) { + members.push(aggregateTransformFlags(createProperty( + /*decorators*/ undefined, + /*modifiers*/ undefined, + parameter.name, + /*questionOrExclamationToken*/ undefined, + /*type*/ undefined, + /*initializer*/ undefined))); + } + } } 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 constructor = getFirstConstructorWithBody(node); - const hasInstancePropertyWithInitializer = forEach(node.members, isInstanceInitializedProperty); - const hasParameterPropertyAssignments = constructor && - constructor.transformFlags & TransformFlags.ContainsTypeScriptClassSyntax && - forEach(constructor.parameters, isParameterWithPropertyAssignment); - - // 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( - /*decorators*/ undefined, - /*modifiers*/ undefined, - parameters, - body - ), - constructor || node - ), - constructor - ) - ); - } - - /** - * Transforms (or creates) the parameters for the constructor of a class with - * parameter property assignments or instance property initializers. - * - * @param constructor The constructor declaration. - */ - function transformConstructorParameters(constructor: ConstructorDeclaration | undefined) { - // 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 - // Let constructor be the result of parsing the source text - // constructor(...args) { super (...args);} - // using the syntactic grammar with the goal symbol MethodDefinition[~Yield]. - // Else, - // Let constructor be the result of parsing the source text - // constructor( ){ } - // using the syntactic grammar with the goal symbol MethodDefinition[~Yield]. - // - // While we could emit the '...args' rest parameter, certain later tools in the pipeline might - // downlevel the '...args' portion less efficiently by naively copying the contents of 'arguments' to an array. - // Instead, we'll avoid using a rest parameter and spread into the super call as - // 'super(...arguments)' instead of 'super(...args)', as you can see in "transformConstructorBody". - return visitParameterList(constructor && constructor.parameters, visitor, context) - || []; - } - - /** - * Transforms (or creates) a constructor body for a class with parameter property - * assignments or instance property initializers. - * - * @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) { - let statements: Statement[] = []; - let indexOfFirstStatement = 0; - - resumeLexicalEnvironment(); - - if (constructor) { - indexOfFirstStatement = addPrologueDirectivesAndInitialSuperCall(constructor, statements); - - // 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: - // - // public x = 1; - // - // Into this: - // - // constructor() { - // this.x = 1; - // } - // - const properties = getInitializedProperties(node, /*isStatic*/ false); - addInitializedPropertyStatements(statements, properties, createThis()); - - 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)); - } - - // End the lexical environment. - statements = mergeLexicalEnvironment(statements, endLexicalEnvironment()); - return setTextRange( - createBlock( - setTextRange( - createNodeArray(statements), - /*location*/ constructor ? constructor.body!.statements : node.members - ), - /*multiLine*/ true - ), - /*location*/ constructor ? constructor.body : undefined - ); - } - - /** - * 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; - const propertyName = getMutableClone(name); - setEmitFlags(propertyName, EmitFlags.NoComments | EmitFlags.NoSourceMap); - - const localName = getMutableClone(name); - setEmitFlags(localName, EmitFlags.NoComments); - - return startOnNewLine( - setEmitFlags( - setTextRange( - createExpressionStatement( - createAssignment( - setTextRange( - createPropertyAccess( - createThis(), - propertyName - ), - node.name - ), - localName - ) - ), - moveRangePos(node, -1) - ), - EmitFlags.NoComments - ) - ); - } - - /** - * 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 @@ -2144,16 +1780,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 +1801,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 +1810,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); } /** @@ -2257,16 +1865,27 @@ namespace ts { * * @param node The declaration node. */ - function shouldEmitFunctionLikeDeclaration(node: FunctionLikeDeclaration) { + function shouldEmitFunctionLikeDeclaration(node: T): node is T & { body: NonNullable } { 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) { @@ -2276,10 +1895,90 @@ namespace ts { return updateConstructor( node, - visitNodes(node.decorators, visitor, isDecorator), - visitNodes(node.modifiers, visitor, isModifier), + /*decorators*/ undefined, + /*modifiers*/ undefined, visitParameterList(node.parameters, visitor, context), - visitFunctionBody(node.body, visitor, context) + transformConstructorBody(node.body, node) + ); + } + + function transformConstructorBody(body: Block, constructor: ConstructorDeclaration) { + const parametersWithPropertyAssignments = constructor && + filter(constructor.parameters, isParameterPropertyDeclaration); + if (!some(parametersWithPropertyAssignments)) { + return visitFunctionBody(body, visitor, context); + } + + let statements: Statement[] = []; + let indexOfFirstStatement = 0; + + resumeLexicalEnvironment(); + + 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; + // } + // + addRange(statements, map(parametersWithPropertyAssignments, transformParameterWithPropertyAssignment)); + + // Add the existing statements, skipping the initial super call. + addRange(statements, visitNodes(body.statements, visitor, isStatement, indexOfFirstStatement)); + + // End the lexical environment. + statements = mergeLexicalEnvironment(statements, endLexicalEnvironment()); + const block = createBlock(setTextRange(createNodeArray(statements), body.statements), /*multiLine*/ true); + setTextRange(block, /*location*/ body); + setOriginalNode(block, body); + return block; + } + + /** + * Transforms a parameter into a property assignment statement. + * + * @param node The parameter declaration. + */ + function transformParameterWithPropertyAssignment(node: ParameterPropertyDeclaration) { + const name = node.name; + if (!isIdentifier(name)) { + return undefined; + } + + const propertyName = getMutableClone(name); + setEmitFlags(propertyName, EmitFlags.NoComments | EmitFlags.NoSourceMap); + + const localName = getMutableClone(name); + setEmitFlags(localName, EmitFlags.NoComments); + + return startOnNewLine( + removeAllComments( + setTextRange( + setOriginalNode( + createExpressionStatement( + createAssignment( + setTextRange( + createPropertyAccess( + createThis(), + propertyName + ), + node.name + ), + localName + ) + ), + node + ), + moveRangePos(node, -1) + ) + ) ); } @@ -2418,6 +2117,7 @@ namespace ts { if (parameterIsThisKeyword(node)) { return undefined; } + const updated = updateParameter( node, /*decorators*/ undefined, 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..29911f42906 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,94 @@ 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; + /* @internal */ locale?: string; + + [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>, resolved: ResolvedConfigFileName): Map { + function getOrCreateValueMapFromConfigFileMap(configFileMap: ConfigFileMap>, resolved: ResolvedConfigFilePath): Map { return getOrCreateValueFromConfigFileMap>(configFileMap, resolved, createMap); } @@ -285,10 +240,20 @@ namespace ts { return fileExtensionIs(fileName, Extension.Dts); } + export type ReportEmitErrorSummary = (errorCount: number) => void; + export interface SolutionBuilderHostBase extends ProgramHost { + createDirectory?(path: string): void; + /** + * Should provide create directory and writeFile if done of invalidatedProjects is not invoked with + * writeFileCallback + */ + writeFile?(path: string, data: string, writeByteOrderMark?: boolean): void; + getModifiedTime(fileName: string): Date | undefined; setModifiedTime(fileName: string, date: Date): void; deleteFile(fileName: string): void; + getParsedCommandLine?(fileName: string): ParsedCommandLine | undefined; reportDiagnostic: DiagnosticReporter; // Technically we want to move it out and allow steps of actions on Solution, but for now just merge stuff in build host here reportSolutionBuilderStatus: DiagnosticReporter; @@ -298,7 +263,7 @@ namespace ts { afterProgramEmitAndDiagnostics?(program: T): void; // For testing - now?(): Date; + /*@internal*/ now?(): Date; } export interface SolutionBuilderHost extends SolutionBuilderHostBase { @@ -308,23 +273,20 @@ namespace ts { export interface SolutionBuilderWithWatchHost extends SolutionBuilderHostBase, WatchHost { } - export interface SolutionBuilder { - buildAllProjects(): ExitStatus; - cleanAllProjects(): ExitStatus; + export interface SolutionBuilder { + build(project?: string, cancellationToken?: CancellationToken): ExitStatus; + clean(project?: string): ExitStatus; + buildReferences(project: string, cancellationToken?: CancellationToken): ExitStatus; + cleanReferences(project?: string): ExitStatus; + getNextInvalidatedProject(cancellationToken?: CancellationToken): InvalidatedProject | undefined; - // TODO:: All the below ones should technically only be in watch mode. but thats for later time - /*@internal*/ resolveProjectName(name: string): ResolvedConfigFileName; - /*@internal*/ getUpToDateStatusOfFile(configFileName: ResolvedConfigFileName): UpToDateStatus; - /*@internal*/ getBuildGraph(configFileNames: ReadonlyArray): DependencyGraph; + // Currently used for testing but can be made public if needed: + /*@internal*/ getBuildOrder(): ReadonlyArray; - /*@internal*/ invalidateProject(configFileName: string, reloadLevel?: ConfigFileProgramReloadLevel): void; - /*@internal*/ buildInvalidatedProject(): void; - - /*@internal*/ resetBuildContext(opts?: BuildOptions): void; - } - - export interface SolutionBuilderWithWatch extends SolutionBuilder { - /*@internal*/ startWatching(): void; + // Testing only + /*@internal*/ getUpToDateStatusOfProject(project: string): UpToDateStatus; + /*@internal*/ invalidateProject(configFilePath: ResolvedConfigFilePath, reloadLevel?: ConfigFileProgramReloadLevel): void; + /*@internal*/ buildNextInvalidatedProject(): void; } /** @@ -369,823 +331,687 @@ namespace ts { return result; } - /** - * A SolutionBuilder has an immutable set of rootNames that are the "entry point" projects, but - * can dynamically add/remove other projects based on changes on the rootNames' references - * TODO: use SolutionBuilderWithWatchHost => watchedSolution - * use SolutionBuilderHost => Solution - */ - export function createSolutionBuilder(host: SolutionBuilderHost, rootNames: ReadonlyArray, defaultOptions: BuildOptions): SolutionBuilder; - export function createSolutionBuilder(host: SolutionBuilderWithWatchHost, rootNames: ReadonlyArray, defaultOptions: BuildOptions): SolutionBuilderWithWatch; - export function createSolutionBuilder(host: SolutionBuilderHost | SolutionBuilderWithWatchHost, rootNames: ReadonlyArray, defaultOptions: BuildOptions): SolutionBuilderWithWatch { - const hostWithWatch = host as SolutionBuilderWithWatchHost; + export function createSolutionBuilder(host: SolutionBuilderHost, rootNames: ReadonlyArray, defaultOptions: BuildOptions): SolutionBuilder { + return createSolutionBuilderWorker(/*watch*/ false, host, rootNames, defaultOptions); + } + + export function createSolutionBuilderWithWatch(host: SolutionBuilderWithWatchHost, rootNames: ReadonlyArray, defaultOptions: BuildOptions): SolutionBuilder { + return createSolutionBuilderWorker(/*watch*/ true, host, rootNames, defaultOptions); + } + + type ConfigFileCacheEntry = ParsedCommandLine | Diagnostic; + interface SolutionBuilderStateCache { + originalReadFile: CompilerHost["readFile"]; + originalFileExists: CompilerHost["fileExists"]; + originalDirectoryExists: CompilerHost["directoryExists"]; + originalCreateDirectory: CompilerHost["createDirectory"]; + originalWriteFile: CompilerHost["writeFile"] | undefined; + originalReadFileWithCache: CompilerHost["readFile"]; + originalGetSourceFile: CompilerHost["getSourceFile"]; + } + + interface SolutionBuilderState { + readonly host: SolutionBuilderHost; + readonly hostWithWatch: SolutionBuilderWithWatchHost; + readonly currentDirectory: string; + readonly getCanonicalFileName: GetCanonicalFileName; + readonly parseConfigFileHost: ParseConfigFileHost; + readonly writeFileName: ((s: string) => void) | undefined; + + // State of solution + readonly options: BuildOptions; + readonly baseCompilerOptions: CompilerOptions; + readonly rootNames: ReadonlyArray; + + readonly resolvedConfigFilePaths: Map; + readonly configFileCache: ConfigFileMap; + /** Map from config file name to up-to-date status */ + readonly projectStatus: ConfigFileMap; + readonly buildInfoChecked: ConfigFileMap; + readonly extendedConfigCache: Map; + + readonly builderPrograms: ConfigFileMap; + readonly diagnostics: ConfigFileMap; + readonly projectPendingBuild: ConfigFileMap; + readonly projectErrorsReported: ConfigFileMap; + + readonly compilerHost: CompilerHost; + readonly moduleResolutionCache: ModuleResolutionCache | undefined; + + // Mutable state + buildOrder: readonly ResolvedConfigFileName[] | undefined; + readFileWithCache: (f: string) => string | undefined; + projectCompilerOptions: CompilerOptions; + cache: SolutionBuilderStateCache | undefined; + allProjectBuildPending: boolean; + needsSummary: boolean; + watchAllProjectsPending: boolean; + currentInvalidatedProject: InvalidatedProject | undefined; + + // Watch state + readonly watch: boolean; + readonly allWatchedWildcardDirectories: ConfigFileMap>; + readonly allWatchedInputFiles: ConfigFileMap>; + readonly allWatchedConfigFiles: ConfigFileMap; + + timerToBuildInvalidatedProject: any; + reportFileChangeDetected: boolean; + watchFile: WatchFile; + watchFilePath: WatchFilePath; + watchDirectory: WatchDirectory; + writeLog: (s: string) => void; + } + + function createSolutionBuilderState(watch: boolean, hostOrHostWithWatch: SolutionBuilderHost | SolutionBuilderWithWatchHost, rootNames: ReadonlyArray, options: BuildOptions): SolutionBuilderState { + const host = hostOrHostWithWatch as SolutionBuilderHost; + const hostWithWatch = hostOrHostWithWatch as SolutionBuilderWithWatchHost; const currentDirectory = host.getCurrentDirectory(); const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames()); - const parseConfigFileHost = parseConfigHostFromCompilerHostLike(host); // State of the solution - let options = defaultOptions; - let baseCompilerOptions = getCompilerOptionsOfBuildOptions(options); - type ConfigFileCacheEntry = ParsedCommandLine | Diagnostic; - const configFileCache = createFileMap(toPath); - /** Map from output file name to its pre-build timestamp */ - const unchangedOutputs = createFileMap(toPath as ToPath); - /** Map from config file name to up-to-date status */ - const projectStatus = createFileMap(toPath); - const missingRoots = createMap(); - let globalDependencyGraph: DependencyGraph | undefined; - const writeFileName = host.trace ? (s: string) => host.trace!(s) : undefined; - let readFileWithCache = (f: string) => host.readFile(f); - let projectCompilerOptions = baseCompilerOptions; - const compilerHost = createCompilerHostFromProgramHost(host, () => projectCompilerOptions); + const baseCompilerOptions = getCompilerOptionsOfBuildOptions(options); + const compilerHost = createCompilerHostFromProgramHost(host, () => state.projectCompilerOptions); setGetSourceFileAsHashVersioned(compilerHost, host); - compilerHost.getParsedCommandLine = parseConfigFile; - + compilerHost.getParsedCommandLine = fileName => parseConfigFile(state, fileName as ResolvedConfigFileName, toResolvedConfigFilePath(state, fileName as ResolvedConfigFileName)); compilerHost.resolveModuleNames = maybeBind(host, host.resolveModuleNames); compilerHost.resolveTypeReferenceDirectives = maybeBind(host, host.resolveTypeReferenceDirectives); const moduleResolutionCache = !compilerHost.resolveModuleNames ? createModuleResolutionCache(currentDirectory, getCanonicalFileName) : undefined; - let cacheState: { - originalReadFile: CompilerHost["readFile"]; - originalFileExists: CompilerHost["fileExists"]; - originalDirectoryExists: CompilerHost["directoryExists"]; - originalCreateDirectory: CompilerHost["createDirectory"]; - originalWriteFile: CompilerHost["writeFile"] | undefined; - originalReadFileWithCache: CompilerHost["readFile"]; - originalGetSourceFile: CompilerHost["getSourceFile"]; - originalResolveModuleNames: CompilerHost["resolveModuleNames"]; - } | undefined; + if (!compilerHost.resolveModuleNames) { + const loader = (moduleName: string, containingFile: string, redirectedReference: ResolvedProjectReference | undefined) => resolveModuleName(moduleName, containingFile, state.projectCompilerOptions, compilerHost, moduleResolutionCache, redirectedReference).resolvedModule!; + compilerHost.resolveModuleNames = (moduleNames, containingFile, _reusedNames, redirectedReference) => + loadWithLocalCache(Debug.assertEachDefined(moduleNames), containingFile, redirectedReference, loader); + } - const buildInfoChecked = createFileMap(toPath); - const extendedConfigCache = createMap(); + const { watchFile, watchFilePath, watchDirectory, writeLog } = createWatchFactory(hostWithWatch, options); - // Watch state - const builderPrograms = createFileMap(toPath); - const diagnostics = createFileMap>(toPath); - const projectPendingBuild = createFileMap(toPath); - const projectErrorsReported = createFileMap(toPath); - const invalidatedProjectQueue = [] as ResolvedConfigFileName[]; - let nextProjectToBuild = 0; - let timerToBuildInvalidatedProject: any; - let reportFileChangeDetected = false; - const { watchFile, watchFilePath, watchDirectory, writeLog } = createWatchFactory(host, options); + const state: SolutionBuilderState = { + host, + hostWithWatch, + currentDirectory, + getCanonicalFileName, + parseConfigFileHost: parseConfigHostFromCompilerHostLike(host), + writeFileName: host.trace ? (s: string) => host.trace!(s) : undefined, - // Watches for the solution - const allWatchedWildcardDirectories = createFileMap>(toPath); - const allWatchedInputFiles = createFileMap>(toPath); - const allWatchedConfigFiles = createFileMap(toPath); + // State of solution + options, + baseCompilerOptions, + rootNames, - return { - buildAllProjects, - getUpToDateStatusOfFile, - cleanAllProjects, - resetBuildContext, - getBuildGraph, + resolvedConfigFilePaths: createMap(), + configFileCache: createConfigFileMap(), + projectStatus: createConfigFileMap(), + buildInfoChecked: createConfigFileMap(), + extendedConfigCache: createMap(), - invalidateProject, - buildInvalidatedProject, + builderPrograms: createConfigFileMap(), + diagnostics: createConfigFileMap(), + projectPendingBuild: createConfigFileMap(), + projectErrorsReported: createConfigFileMap(), - resolveProjectName, + compilerHost, + moduleResolutionCache, - startWatching + // Mutable state + buildOrder: undefined, + readFileWithCache: f => host.readFile(f), + projectCompilerOptions: baseCompilerOptions, + cache: undefined, + allProjectBuildPending: true, + needsSummary: true, + watchAllProjectsPending: watch, + currentInvalidatedProject: undefined, + + // Watch state + watch, + allWatchedWildcardDirectories: createConfigFileMap(), + allWatchedInputFiles: createConfigFileMap(), + allWatchedConfigFiles: createConfigFileMap(), + + timerToBuildInvalidatedProject: undefined, + reportFileChangeDetected: false, + watchFile, + watchFilePath, + watchDirectory, + writeLog, }; - function toPath(fileName: ResolvedConfigFileName): ResolvedConfigFilePath; - function toPath(fileName: string): Path; - function toPath(fileName: string) { - return ts.toPath(fileName, currentDirectory, getCanonicalFileName); + return state; + } + + function toPath(state: SolutionBuilderState, fileName: string) { + return ts.toPath(fileName, state.currentDirectory, state.getCanonicalFileName); + } + + function toResolvedConfigFilePath(state: SolutionBuilderState, fileName: ResolvedConfigFileName): ResolvedConfigFilePath { + const { resolvedConfigFilePaths } = state; + const path = resolvedConfigFilePaths.get(fileName); + if (path !== undefined) return path; + + const resolvedPath = toPath(state, fileName) as ResolvedConfigFilePath; + resolvedConfigFilePaths.set(fileName, resolvedPath); + return resolvedPath; + } + + function isParsedCommandLine(entry: ConfigFileCacheEntry): entry is ParsedCommandLine { + return !!(entry as ParsedCommandLine).options; + } + + function parseConfigFile(state: SolutionBuilderState, configFileName: ResolvedConfigFileName, configFilePath: ResolvedConfigFilePath): ParsedCommandLine | undefined { + const { configFileCache } = state; + const value = configFileCache.get(configFilePath); + if (value) { + return isParsedCommandLine(value) ? value : undefined; } - function resetBuildContext(opts = defaultOptions) { - options = opts; - baseCompilerOptions = getCompilerOptionsOfBuildOptions(options); - configFileCache.clear(); - unchangedOutputs.clear(); - projectStatus.clear(); - missingRoots.clear(); - globalDependencyGraph = undefined; - buildInfoChecked.clear(); - - diagnostics.clear(); - projectPendingBuild.clear(); - projectErrorsReported.clear(); - invalidatedProjectQueue.length = 0; - nextProjectToBuild = 0; - if (timerToBuildInvalidatedProject) { - clearTimeout(timerToBuildInvalidatedProject); - timerToBuildInvalidatedProject = undefined; - } - reportFileChangeDetected = false; - clearMap(allWatchedWildcardDirectories, wildCardWatches => clearMap(wildCardWatches, closeFileWatcherOf)); - clearMap(allWatchedInputFiles, inputFileWatches => clearMap(inputFileWatches, closeFileWatcher)); - clearMap(allWatchedConfigFiles, closeFileWatcher); - builderPrograms.clear(); + let diagnostic: Diagnostic | undefined; + const { parseConfigFileHost, baseCompilerOptions, extendedConfigCache, host } = state; + let parsed: ParsedCommandLine | undefined; + if (host.getParsedCommandLine) { + parsed = host.getParsedCommandLine(configFileName); + if (!parsed) diagnostic = createCompilerDiagnostic(Diagnostics.File_0_not_found, configFileName); } - - function isParsedCommandLine(entry: ConfigFileCacheEntry): entry is ParsedCommandLine { - return !!(entry as ParsedCommandLine).options; - } - - function parseConfigFile(configFilePath: ResolvedConfigFileName): ParsedCommandLine | undefined { - const value = configFileCache.getValue(configFilePath); - if (value) { - return isParsedCommandLine(value) ? value : undefined; - } - - let diagnostic: Diagnostic | undefined; + else { parseConfigFileHost.onUnRecoverableConfigFileDiagnostic = d => diagnostic = d; - const parsed = getParsedCommandLineOfConfigFile(configFilePath, baseCompilerOptions, parseConfigFileHost, extendedConfigCache); + parsed = getParsedCommandLineOfConfigFile(configFileName, baseCompilerOptions, parseConfigFileHost, extendedConfigCache); parseConfigFileHost.onUnRecoverableConfigFileDiagnostic = noop; - configFileCache.setValue(configFilePath, parsed || diagnostic!); - return parsed; + } + configFileCache.set(configFilePath, parsed || diagnostic!); + return parsed; + } + + function resolveProjectName(state: SolutionBuilderState, name: string): ResolvedConfigFileName { + return resolveConfigFileProjectName(resolvePath(state.currentDirectory, name)); + } + + function createBuildOrder(state: SolutionBuilderState, roots: readonly ResolvedConfigFileName[]): readonly ResolvedConfigFileName[] { + const temporaryMarks = createMap() as ConfigFileMap; + const permanentMarks = createMap() as ConfigFileMap; + const circularityReportStack: string[] = []; + let buildOrder: ResolvedConfigFileName[] | undefined; + for (const root of roots) { + visit(root); } - function reportStatus(message: DiagnosticMessage, ...args: string[]) { - host.reportSolutionBuilderStatus(createCompilerDiagnostic(message, ...args)); - } + return buildOrder || emptyArray; - function reportWatchStatus(message: DiagnosticMessage, ...args: (string | number | undefined)[]) { - if (hostWithWatch.onWatchStatusChange) { - hostWithWatch.onWatchStatusChange(createCompilerDiagnostic(message, ...args), host.getNewLine(), baseCompilerOptions); + function visit(configFileName: ResolvedConfigFileName, inCircularContext?: boolean) { + const projPath = toResolvedConfigFilePath(state, configFileName); + // Already visited + if (permanentMarks.has(projPath)) return; + // Circular + if (temporaryMarks.has(projPath)) { + if (!inCircularContext) { + // TODO:: Do we report this as error? + reportStatus(state, Diagnostics.Project_references_may_not_form_a_circular_graph_Cycle_detected_Colon_0, circularityReportStack.join("\r\n")); + } + return; } - } - function startWatching() { - const graph = getGlobalDependencyGraph(); - for (const resolved of graph.buildQueue) { - // Watch this file - watchConfigFile(resolved); - - const cfg = parseConfigFile(resolved); - if (cfg) { - // Update watchers for wildcard directories - watchWildCardDirectories(resolved, cfg); - - // Watch input files - watchInputFiles(resolved, cfg); + temporaryMarks.set(projPath, true); + circularityReportStack.push(configFileName); + const parsed = parseConfigFile(state, configFileName, projPath); + if (parsed && parsed.projectReferences) { + for (const ref of parsed.projectReferences) { + const resolvedRefPath = resolveProjectName(state, ref.path); + visit(resolvedRefPath, inCircularContext || ref.circular); } } + circularityReportStack.pop(); + permanentMarks.set(projPath, true); + (buildOrder || (buildOrder = [])).push(configFileName); } + } - function watchConfigFile(resolved: ResolvedConfigFileName) { - if (options.watch && !allWatchedConfigFiles.hasKey(resolved)) { - allWatchedConfigFiles.setValue(resolved, watchFile( - hostWithWatch, - resolved, - () => { - invalidateProjectAndScheduleBuilds(resolved, ConfigFileProgramReloadLevel.Full); - }, - PollingInterval.High, - WatchType.ConfigFile, - resolved - )); - } - } + function getBuildOrder(state: SolutionBuilderState) { + return state.buildOrder || + (state.buildOrder = createBuildOrder(state, state.rootNames.map(f => resolveProjectName(state, f)))); + } - function watchWildCardDirectories(resolved: ResolvedConfigFileName, parsed: ParsedCommandLine) { - if (!options.watch) return; - updateWatchingWildcardDirectories( - getOrCreateValueMapFromConfigFileMap(allWatchedWildcardDirectories, resolved), - createMapFromTemplate(parsed.configFileSpecs!.wildcardDirectories), - (dir, flags) => { - return watchDirectory( - hostWithWatch, - dir, - fileOrDirectory => { - const fileOrDirectoryPath = toPath(fileOrDirectory); - if (fileOrDirectoryPath !== toPath(dir) && hasExtension(fileOrDirectoryPath) && !isSupportedSourceFileName(fileOrDirectory, parsed.options)) { - writeLog(`Project: ${resolved} Detected file add/remove of non supported extension: ${fileOrDirectory}`); - return; - } - - if (isOutputFile(fileOrDirectory, parsed)) { - writeLog(`${fileOrDirectory} is output file`); - return; - } - - invalidateProjectAndScheduleBuilds(resolved, ConfigFileProgramReloadLevel.Partial); - }, - flags, - WatchType.WildcardDirectory, - resolved - ); - } + function getBuildOrderFor(state: SolutionBuilderState, project: string | undefined, onlyReferences: boolean | undefined) { + const resolvedProject = project && resolveProjectName(state, project); + const buildOrderFromState = getBuildOrder(state); + if (resolvedProject) { + const projectPath = toResolvedConfigFilePath(state, resolvedProject); + const projectIndex = findIndex( + buildOrderFromState, + configFileName => toResolvedConfigFilePath(state, configFileName) === projectPath ); + if (projectIndex === -1) return undefined; + } + const buildOrder = resolvedProject ? createBuildOrder(state, [resolvedProject]) : buildOrderFromState; + Debug.assert(!onlyReferences || resolvedProject !== undefined); + Debug.assert(!onlyReferences || buildOrder[buildOrder.length - 1] === resolvedProject); + return onlyReferences ? buildOrder.slice(0, buildOrder.length - 1) : buildOrder; + } + + function enableCache(state: SolutionBuilderState) { + if (state.cache) { + disableCache(state); } - function watchInputFiles(resolved: ResolvedConfigFileName, parsed: ParsedCommandLine) { - if (!options.watch) return; - mutateMap( - getOrCreateValueMapFromConfigFileMap(allWatchedInputFiles, resolved), - arrayToMap(parsed.fileNames, toPath), - { - createNewValue: (path, input) => watchFilePath( - hostWithWatch, - input, - () => invalidateProjectAndScheduleBuilds(resolved, ConfigFileProgramReloadLevel.None), - PollingInterval.Low, - path as Path, - WatchType.SourceFile, - resolved + const { compilerHost, host } = state; + + const originalReadFileWithCache = state.readFileWithCache; + const originalGetSourceFile = compilerHost.getSourceFile; + + const { + originalReadFile, originalFileExists, originalDirectoryExists, + originalCreateDirectory, originalWriteFile, + getSourceFileWithCache, readFileWithCache + } = changeCompilerHostLikeToUseCache( + host, + fileName => toPath(state, fileName), + (...args) => originalGetSourceFile.call(compilerHost, ...args) + ); + state.readFileWithCache = readFileWithCache; + compilerHost.getSourceFile = getSourceFileWithCache!; + + state.cache = { + originalReadFile, + originalFileExists, + originalDirectoryExists, + originalCreateDirectory, + originalWriteFile, + originalReadFileWithCache, + originalGetSourceFile, + }; + } + + function disableCache(state: SolutionBuilderState) { + if (!state.cache) return; + + const { cache, host, compilerHost, extendedConfigCache, moduleResolutionCache } = state; + + host.readFile = cache.originalReadFile; + host.fileExists = cache.originalFileExists; + host.directoryExists = cache.originalDirectoryExists; + host.createDirectory = cache.originalCreateDirectory; + host.writeFile = cache.originalWriteFile; + compilerHost.getSourceFile = cache.originalGetSourceFile; + state.readFileWithCache = cache.originalReadFileWithCache; + extendedConfigCache.clear(); + if (moduleResolutionCache) { + moduleResolutionCache.directoryToModuleNameMap.clear(); + moduleResolutionCache.moduleNameToDirectoryMap.clear(); + } + state.cache = undefined; + } + + function clearProjectStatus(state: SolutionBuilderState, resolved: ResolvedConfigFilePath) { + state.projectStatus.delete(resolved); + state.diagnostics.delete(resolved); + } + + function addProjToQueue({ projectPendingBuild }: SolutionBuilderState, proj: ResolvedConfigFilePath, reloadLevel: ConfigFileProgramReloadLevel) { + const value = projectPendingBuild.get(proj); + if (value === undefined) { + projectPendingBuild.set(proj, reloadLevel); + } + else if (value < reloadLevel) { + projectPendingBuild.set(proj, reloadLevel); + } + } + + function setupInitialBuild(state: SolutionBuilderState, cancellationToken: CancellationToken | undefined) { + // Set initial build if not already built + if (!state.allProjectBuildPending) return; + state.allProjectBuildPending = false; + if (state.options.watch) { reportWatchStatus(state, Diagnostics.Starting_compilation_in_watch_mode); } + enableCache(state); + const buildOrder = getBuildOrder(state); + buildOrder.forEach(configFileName => + state.projectPendingBuild.set( + toResolvedConfigFilePath(state, configFileName), + ConfigFileProgramReloadLevel.None + ) + ); + + if (cancellationToken) { + cancellationToken.throwIfCancellationRequested(); + } + } + + export enum InvalidatedProjectKind { + Build, + UpdateBundle, + UpdateOutputFileStamps + } + + export interface InvalidatedProjectBase { + readonly kind: InvalidatedProjectKind; + readonly project: ResolvedConfigFileName; + /*@internal*/ readonly projectPath: ResolvedConfigFilePath; + /*@internal*/ readonly buildOrder: readonly ResolvedConfigFileName[]; + /** + * To dispose this project and ensure that all the necessary actions are taken and state is updated accordingly + */ + done(cancellationToken?: CancellationToken, writeFile?: WriteFileCallback, customTransformers?: CustomTransformers): ExitStatus; + getCompilerOptions(): CompilerOptions; + getCurrentDirectory(): string; + } + + export interface UpdateOutputFileStampsProject extends InvalidatedProjectBase { + readonly kind: InvalidatedProjectKind.UpdateOutputFileStamps; + updateOutputFileStatmps(): void; + } + + export interface BuildInvalidedProject extends InvalidatedProjectBase { + readonly kind: InvalidatedProjectKind.Build; + /* + * Emitting with this builder program without the api provided for this project + * can result in build system going into invalid state as files written reflect the state of the project + */ + getBuilderProgram(): T | undefined; + getProgram(): Program | undefined; + getSourceFile(fileName: string): SourceFile | undefined; + getSourceFiles(): ReadonlyArray; + getOptionsDiagnostics(cancellationToken?: CancellationToken): ReadonlyArray; + getGlobalDiagnostics(cancellationToken?: CancellationToken): ReadonlyArray; + getConfigFileParsingDiagnostics(): ReadonlyArray; + getSyntacticDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray; + getAllDependencies(sourceFile: SourceFile): ReadonlyArray; + getSemanticDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray; + getSemanticDiagnosticsOfNextAffectedFile(cancellationToken?: CancellationToken, ignoreSourceFile?: (sourceFile: SourceFile) => boolean): AffectedFileResult>; + /* + * Calling emit directly with targetSourceFile and emitOnlyDtsFiles set to true is not advised since + * emit in build system is responsible in updating status of the project + * If called with targetSourceFile and emitOnlyDtsFiles set to true, the emit just passes to underlying builder and + * wont reflect the status of file as being emitted in the builder + * (if that emit of that source file is required it would be emitted again when making sure invalidated project is completed) + * This emit is not considered actual emit (and hence uptodate status is not reflected if + */ + emit(targetSourceFile?: SourceFile, writeFile?: WriteFileCallback, cancellationToken?: CancellationToken, emitOnlyDtsFiles?: boolean, customTransformers?: CustomTransformers): EmitResult | undefined; + // TODO(shkamat):: investigate later if we can emit even when there are declaration diagnostics + // emitNextAffectedFile(writeFile?: WriteFileCallback, cancellationToken?: CancellationToken, customTransformers?: CustomTransformers): AffectedFileResult; + } + + export interface UpdateBundleProject extends InvalidatedProjectBase { + readonly kind: InvalidatedProjectKind.UpdateBundle; + emit(writeFile?: WriteFileCallback, customTransformers?: CustomTransformers): EmitResult | BuildInvalidedProject | undefined; + } + + export type InvalidatedProject = UpdateOutputFileStampsProject | BuildInvalidedProject | UpdateBundleProject; + + function doneInvalidatedProject( + state: SolutionBuilderState, + projectPath: ResolvedConfigFilePath + ) { + state.projectPendingBuild.delete(projectPath); + state.currentInvalidatedProject = undefined; + return state.diagnostics.has(projectPath) ? + ExitStatus.DiagnosticsPresent_OutputsSkipped : + ExitStatus.Success; + } + + function createUpdateOutputFileStampsProject( + state: SolutionBuilderState, + project: ResolvedConfigFileName, + projectPath: ResolvedConfigFilePath, + config: ParsedCommandLine, + buildOrder: readonly ResolvedConfigFileName[] + ): UpdateOutputFileStampsProject { + let updateOutputFileStampsPending = true; + return { + kind: InvalidatedProjectKind.UpdateOutputFileStamps, + project, + projectPath, + buildOrder, + getCompilerOptions: () => config.options, + getCurrentDirectory: () => state.currentDirectory, + updateOutputFileStatmps: () => { + updateOutputTimestamps(state, config, projectPath); + updateOutputFileStampsPending = false; + }, + done: () => { + if (updateOutputFileStampsPending) { + updateOutputTimestamps(state, config, projectPath); + } + return doneInvalidatedProject(state, projectPath); + } + }; + } + + function createBuildOrUpdateInvalidedProject( + kind: InvalidatedProjectKind.Build | InvalidatedProjectKind.UpdateBundle, + state: SolutionBuilderState, + project: ResolvedConfigFileName, + projectPath: ResolvedConfigFilePath, + projectIndex: number, + config: ParsedCommandLine, + buildOrder: readonly ResolvedConfigFileName[], + ): BuildInvalidedProject | UpdateBundleProject { + enum Step { + CreateProgram, + SyntaxDiagnostics, + SemanticDiagnostics, + Emit, + EmitBundle, + BuildInvalidatedProjectOfBundle, + QueueReferencingProjects, + Done + } + + let step = kind === InvalidatedProjectKind.Build ? Step.CreateProgram : Step.EmitBundle; + let program: T | undefined; + let buildResult: BuildResultFlags | undefined; + let invalidatedProjectOfBundle: BuildInvalidedProject | undefined; + + return kind === InvalidatedProjectKind.Build ? + { + kind, + project, + projectPath, + buildOrder, + getCompilerOptions: () => config.options, + getCurrentDirectory: () => state.currentDirectory, + getBuilderProgram: () => withProgramOrUndefined(identity), + getProgram: () => + withProgramOrUndefined( + program => program.getProgramOrUndefined() ), - onDeleteValue: closeFileWatcher, - } + getSourceFile: fileName => + withProgramOrUndefined( + program => program.getSourceFile(fileName) + ), + getSourceFiles: () => + withProgramOrEmptyArray( + program => program.getSourceFiles() + ), + getOptionsDiagnostics: cancellationToken => + withProgramOrEmptyArray( + program => program.getOptionsDiagnostics(cancellationToken) + ), + getGlobalDiagnostics: cancellationToken => + withProgramOrEmptyArray( + program => program.getGlobalDiagnostics(cancellationToken) + ), + getConfigFileParsingDiagnostics: () => + withProgramOrEmptyArray( + program => program.getConfigFileParsingDiagnostics() + ), + getSyntacticDiagnostics: (sourceFile, cancellationToken) => + withProgramOrEmptyArray( + program => program.getSyntacticDiagnostics(sourceFile, cancellationToken) + ), + getAllDependencies: sourceFile => + withProgramOrEmptyArray( + program => program.getAllDependencies(sourceFile) + ), + getSemanticDiagnostics: (sourceFile, cancellationToken) => + withProgramOrEmptyArray( + program => program.getSemanticDiagnostics(sourceFile, cancellationToken) + ), + getSemanticDiagnosticsOfNextAffectedFile: (cancellationToken, ignoreSourceFile) => + withProgramOrUndefined( + program => + ((program as any as SemanticDiagnosticsBuilderProgram).getSemanticDiagnosticsOfNextAffectedFile) && + (program as any as SemanticDiagnosticsBuilderProgram).getSemanticDiagnosticsOfNextAffectedFile(cancellationToken, ignoreSourceFile) + ), + emit: (targetSourceFile, writeFile, cancellationToken, emitOnlyDtsFiles, customTransformers) => { + if (targetSourceFile || emitOnlyDtsFiles) { + return withProgramOrUndefined( + program => program.emit(targetSourceFile, writeFile, cancellationToken, emitOnlyDtsFiles, customTransformers) + ); + } + executeSteps(Step.SemanticDiagnostics, cancellationToken); + if (step !== Step.Emit) return undefined; + return emit(writeFile, cancellationToken, customTransformers); + }, + done + } : + { + kind, + project, + projectPath, + buildOrder, + getCompilerOptions: () => config.options, + getCurrentDirectory: () => state.currentDirectory, + emit: (writeFile: WriteFileCallback | undefined, customTransformers: CustomTransformers | undefined) => { + if (step !== Step.EmitBundle) return invalidatedProjectOfBundle; + return emitBundle(writeFile, customTransformers); + }, + done, + }; + + function done(cancellationToken?: CancellationToken, writeFile?: WriteFileCallback, customTransformers?: CustomTransformers) { + executeSteps(Step.Done, cancellationToken, writeFile, customTransformers); + return doneInvalidatedProject(state, projectPath); + } + + function withProgramOrUndefined(action: (program: T) => U | undefined): U | undefined { + executeSteps(Step.CreateProgram); + return program && action(program); + } + + function withProgramOrEmptyArray(action: (program: T) => ReadonlyArray): ReadonlyArray { + return withProgramOrUndefined(action) || emptyArray; + } + + function createProgram() { + Debug.assert(program === undefined); + + if (state.options.dry) { + reportStatus(state, Diagnostics.A_non_dry_build_would_build_project_0, project); + buildResult = BuildResultFlags.Success; + step = Step.QueueReferencingProjects; + return; + } + + if (state.options.verbose) reportStatus(state, Diagnostics.Building_project_0, project); + + if (config.fileNames.length === 0) { + reportAndStoreErrors(state, projectPath, config.errors); + // Nothing to build - must be a solution file, basically + buildResult = BuildResultFlags.None; + step = Step.QueueReferencingProjects; + return; + } + + const { host, compilerHost } = state; + state.projectCompilerOptions = config.options; + // Update module resolution cache if needed + updateModuleResolutionCache(state, project, config); + + // Create program + program = host.createProgram( + config.fileNames, + config.options, + compilerHost, + getOldProgram(state, projectPath, config), + config.errors, + config.projectReferences ); + step++; } - function isOutputFile(fileName: string, configFile: ParsedCommandLine) { - if (configFile.options.noEmit) return false; - - // ts or tsx files are not output - if (!fileExtensionIs(fileName, Extension.Dts) && - (fileExtensionIs(fileName, Extension.Ts) || fileExtensionIs(fileName, Extension.Tsx))) { - return false; - } - - // If options have --outFile or --out, check if its that - const out = configFile.options.outFile || configFile.options.out; - if (out && (isSameFile(fileName, out) || isSameFile(fileName, removeFileExtension(out) + Extension.Dts))) { - return true; - } - - // If declarationDir is specified, return if its a file in that directory - if (configFile.options.declarationDir && containsPath(configFile.options.declarationDir, fileName, currentDirectory, !host.useCaseSensitiveFileNames())) { - return true; - } - - // If --outDir, check if file is in that directory - if (configFile.options.outDir && containsPath(configFile.options.outDir, fileName, currentDirectory, !host.useCaseSensitiveFileNames())) { - return true; - } - - return !forEach(configFile.fileNames, inputFile => isSameFile(fileName, inputFile)); - } - - function isSameFile(file1: string, file2: string) { - return comparePaths(file1, file2, currentDirectory, !host.useCaseSensitiveFileNames()) === Comparison.EqualTo; - } - - function invalidateProjectAndScheduleBuilds(resolved: ResolvedConfigFileName, reloadLevel: ConfigFileProgramReloadLevel) { - reportFileChangeDetected = true; - invalidateResolvedProject(resolved, reloadLevel); - scheduleBuildInvalidatedProject(); - } - - function getUpToDateStatusOfFile(configFileName: ResolvedConfigFileName): UpToDateStatus { - return getUpToDateStatus(parseConfigFile(configFileName)); - } - - function getBuildGraph(configFileNames: ReadonlyArray) { - return createDependencyGraph(resolveProjectNames(configFileNames)); - } - - function getGlobalDependencyGraph() { - return globalDependencyGraph || (globalDependencyGraph = getBuildGraph(rootNames)); - } - - function getUpToDateStatus(project: ParsedCommandLine | undefined): UpToDateStatus { - if (project === undefined) { - return { type: UpToDateStatusType.Unbuildable, reason: "File deleted mid-build" }; - } - - const prior = projectStatus.getValue(project.options.configFilePath as ResolvedConfigFilePath); - if (prior !== undefined) { - return prior; - } - - const actual = getUpToDateStatusWorker(project); - projectStatus.setValue(project.options.configFilePath as ResolvedConfigFilePath, actual); - return actual; - } - - function getUpToDateStatusWorker(project: ParsedCommandLine): UpToDateStatus { - let newestInputFileName: string = undefined!; - let newestInputFileTime = minimumDate; - // Get timestamps of input files - for (const inputFile of project.fileNames) { - if (!host.fileExists(inputFile)) { - return { - type: UpToDateStatusType.Unbuildable, - reason: `${inputFile} does not exist` - }; - } - - const inputTime = host.getModifiedTime(inputFile) || missingFileModifiedTime; - if (inputTime > newestInputFileTime) { - newestInputFileName = inputFile; - newestInputFileTime = inputTime; - } - } - - // Container if no files are specified in the project - if (!project.fileNames.length && !canJsonReportNoInutFiles(project.raw)) { - return { - type: UpToDateStatusType.ContainerOnly - }; - } - - // Collect the expected outputs of this project - const outputs = getAllProjectOutputs(project, !host.useCaseSensitiveFileNames()); - - // Now see if all outputs are newer than the newest input - let oldestOutputFileName = "(none)"; - let oldestOutputFileTime = maximumDate; - let newestOutputFileName = "(none)"; - let newestOutputFileTime = minimumDate; - let missingOutputFileName: string | undefined; - let newestDeclarationFileContentChangedTime = minimumDate; - let isOutOfDateWithInputs = false; - for (const output of outputs) { - // Output is missing; can stop checking - // Don't immediately return because we can still be upstream-blocked, which is a higher-priority status - if (!host.fileExists(output)) { - missingOutputFileName = output; - break; - } - - const outputTime = host.getModifiedTime(output) || missingFileModifiedTime; - if (outputTime < oldestOutputFileTime) { - oldestOutputFileTime = outputTime; - oldestOutputFileName = output; - } - - // If an output is older than the newest input, we can stop checking - // Don't immediately return because we can still be upstream-blocked, which is a higher-priority status - if (outputTime < newestInputFileTime) { - isOutOfDateWithInputs = true; - break; - } - - if (outputTime > newestOutputFileTime) { - newestOutputFileTime = outputTime; - newestOutputFileName = output; - } - - // Keep track of when the most recent time a .d.ts file was changed. - // In addition to file timestamps, we also keep track of when a .d.ts file - // had its file touched but not had its contents changed - this allows us - // to skip a downstream typecheck - if (isDeclarationFile(output)) { - const unchangedTime = unchangedOutputs.getValue(output); - if (unchangedTime !== undefined) { - newestDeclarationFileContentChangedTime = newer(unchangedTime, newestDeclarationFileContentChangedTime); - } - else { - const outputModifiedTime = host.getModifiedTime(output) || missingFileModifiedTime; - newestDeclarationFileContentChangedTime = newer(newestDeclarationFileContentChangedTime, outputModifiedTime); - } - } - } - - let pseudoUpToDate = false; - let usesPrepend = false; - let upstreamChangedProject: string | undefined; - if (project.projectReferences) { - projectStatus.setValue(project.options.configFilePath as ResolvedConfigFileName, { type: UpToDateStatusType.ComputingUpstream }); - for (const ref of project.projectReferences) { - usesPrepend = usesPrepend || !!(ref.prepend); - const resolvedRef = resolveProjectReferencePath(ref); - const refStatus = getUpToDateStatus(parseConfigFile(resolvedRef)); - - // Its a circular reference ignore the status of this project - if (refStatus.type === UpToDateStatusType.ComputingUpstream) { - continue; - } - - // An upstream project is blocked - if (refStatus.type === UpToDateStatusType.Unbuildable) { - return { - type: UpToDateStatusType.UpstreamBlocked, - upstreamProjectName: ref.path - }; - } - - // If the upstream project is out of date, then so are we (someone shouldn't have asked, though?) - if (refStatus.type !== UpToDateStatusType.UpToDate) { - return { - type: UpToDateStatusType.UpstreamOutOfDate, - upstreamProjectName: ref.path - }; - } - - // Check oldest output file name only if there is no missing output file name - if (!missingOutputFileName) { - // If the upstream project's newest file is older than our oldest output, we - // can't be out of date because of it - if (refStatus.newestInputFileTime && refStatus.newestInputFileTime <= oldestOutputFileTime) { - continue; - } - - // If the upstream project has only change .d.ts files, and we've built - // *after* those files, then we're "psuedo up to date" and eligible for a fast rebuild - if (refStatus.newestDeclarationFileContentChangedTime && refStatus.newestDeclarationFileContentChangedTime <= oldestOutputFileTime) { - pseudoUpToDate = true; - upstreamChangedProject = ref.path; - continue; - } - - // We have an output older than an upstream output - we are out of date - Debug.assert(oldestOutputFileName !== undefined, "Should have an oldest output filename here"); - return { - type: UpToDateStatusType.OutOfDateWithUpstream, - outOfDateOutputFileName: oldestOutputFileName, - newerProjectName: ref.path - }; - } - } - } - - if (missingOutputFileName !== undefined) { - return { - type: UpToDateStatusType.OutputMissing, - missingOutputFileName - }; - } - - if (isOutOfDateWithInputs) { - return { - type: UpToDateStatusType.OutOfDateWithSelf, - outOfDateOutputFileName: oldestOutputFileName, - newerInputFileName: newestInputFileName - }; + function handleDiagnostics(diagnostics: ReadonlyArray, errorFlags: BuildResultFlags, errorType: string) { + if (diagnostics.length) { + buildResult = buildErrors( + state, + projectPath, + program, + diagnostics, + errorFlags, + errorType + ); + step = Step.QueueReferencingProjects; } else { - // Check tsconfig time - const configStatus = checkConfigFileUpToDateStatus(project.options.configFilePath!, oldestOutputFileTime, oldestOutputFileName); - if (configStatus) return configStatus; - - // Check extended config time - const extendedConfigStatus = forEach(project.options.configFile!.extendedSourceFiles || emptyArray, configFile => checkConfigFileUpToDateStatus(configFile, oldestOutputFileTime, oldestOutputFileName)); - if (extendedConfigStatus) return extendedConfigStatus; - } - - if (!buildInfoChecked.hasKey(project.options.configFilePath as ResolvedConfigFileName)) { - buildInfoChecked.setValue(project.options.configFilePath as ResolvedConfigFileName, true); - const buildInfoPath = getOutputPathForBuildInfo(project.options); - if (buildInfoPath) { - const value = readFileWithCache(buildInfoPath); - const buildInfo = value && getBuildInfo(value); - if (buildInfo && buildInfo.version !== version) { - return { - type: UpToDateStatusType.TsVersionOutputOfDate, - version: buildInfo.version - }; - } - } - } - - if (usesPrepend && pseudoUpToDate) { - return { - type: UpToDateStatusType.OutOfDateWithPrepend, - outOfDateOutputFileName: oldestOutputFileName, - newerProjectName: upstreamChangedProject! - }; - } - - // Up to date - return { - type: pseudoUpToDate ? UpToDateStatusType.UpToDateWithUpstreamTypes : UpToDateStatusType.UpToDate, - newestDeclarationFileContentChangedTime, - newestInputFileTime, - newestOutputFileTime, - newestInputFileName, - newestOutputFileName, - oldestOutputFileName - }; - } - - function checkConfigFileUpToDateStatus(configFile: string, oldestOutputFileTime: Date, oldestOutputFileName: string): Status.OutOfDateWithSelf | undefined { - // Check tsconfig time - const tsconfigTime = host.getModifiedTime(configFile) || missingFileModifiedTime; - if (oldestOutputFileTime < tsconfigTime) { - return { - type: UpToDateStatusType.OutOfDateWithSelf, - outOfDateOutputFileName: oldestOutputFileName, - newerInputFileName: configFile - }; + step++; } } - function invalidateProject(configFileName: string, reloadLevel?: ConfigFileProgramReloadLevel) { - invalidateResolvedProject(resolveProjectName(configFileName), reloadLevel); - } - - function invalidateResolvedProject(resolved: ResolvedConfigFileName, reloadLevel?: ConfigFileProgramReloadLevel) { - if (reloadLevel === ConfigFileProgramReloadLevel.Full) { - configFileCache.removeKey(resolved); - globalDependencyGraph = undefined; - } - projectStatus.removeKey(resolved); - diagnostics.removeKey(resolved); - - addProjToQueue(resolved, reloadLevel); - enableCache(); - } - - /** - * return true if new addition - */ - function addProjToQueue(proj: ResolvedConfigFileName, reloadLevel?: ConfigFileProgramReloadLevel) { - const value = projectPendingBuild.getValue(proj); - if (value === undefined) { - projectPendingBuild.setValue(proj, reloadLevel || ConfigFileProgramReloadLevel.None); - invalidatedProjectQueue.push(proj); - } - else if (value < (reloadLevel || ConfigFileProgramReloadLevel.None)) { - projectPendingBuild.setValue(proj, reloadLevel || ConfigFileProgramReloadLevel.None); - } - } - - function getNextInvalidatedProject() { - if (nextProjectToBuild < invalidatedProjectQueue.length) { - const project = invalidatedProjectQueue[nextProjectToBuild]; - nextProjectToBuild++; - const reloadLevel = projectPendingBuild.getValue(project)!; - projectPendingBuild.removeKey(project); - if (!projectPendingBuild.getSize()) { - invalidatedProjectQueue.length = 0; - nextProjectToBuild = 0; - } - return { project, reloadLevel }; - } - } - - function hasPendingInvalidatedProjects() { - return !!projectPendingBuild.getSize(); - } - - function scheduleBuildInvalidatedProject() { - if (!hostWithWatch.setTimeout || !hostWithWatch.clearTimeout) { - return; - } - if (timerToBuildInvalidatedProject) { - hostWithWatch.clearTimeout(timerToBuildInvalidatedProject); - } - timerToBuildInvalidatedProject = hostWithWatch.setTimeout(buildInvalidatedProject, 250); - } - - function buildInvalidatedProject() { - timerToBuildInvalidatedProject = undefined; - if (reportFileChangeDetected) { - reportFileChangeDetected = false; - projectErrorsReported.clear(); - reportWatchStatus(Diagnostics.File_change_detected_Starting_incremental_compilation); - } - const buildProject = getNextInvalidatedProject(); - if (buildProject) { - buildSingleInvalidatedProject(buildProject.project, buildProject.reloadLevel); - if (hasPendingInvalidatedProjects()) { - if (options.watch && !timerToBuildInvalidatedProject) { - scheduleBuildInvalidatedProject(); - } - } - else { - disableCache(); - reportErrorSummary(); - } - } - } - - function reportErrorSummary() { - if (options.watch || (host as SolutionBuilderHost).reportErrorSummary) { - // Report errors from the other projects - getGlobalDependencyGraph().buildQueue.forEach(project => { - if (!projectErrorsReported.hasKey(project)) { - reportErrors(diagnostics.getValue(project) || emptyArray); - } - }); - let totalErrors = 0; - diagnostics.forEach(singleProjectErrors => totalErrors += getErrorCountForSummary(singleProjectErrors)); - if (options.watch) { - reportWatchStatus(getWatchErrorSummaryDiagnosticMessage(totalErrors), totalErrors); - } - else { - (host as SolutionBuilderHost).reportErrorSummary!(totalErrors); - } - } - } - - function buildSingleInvalidatedProject(resolved: ResolvedConfigFileName, reloadLevel: ConfigFileProgramReloadLevel) { - const proj = parseConfigFile(resolved); - if (!proj) { - reportParseConfigFileDiagnostic(resolved); - return; - } - - if (reloadLevel === ConfigFileProgramReloadLevel.Full) { - watchConfigFile(resolved); - watchWildCardDirectories(resolved, proj); - watchInputFiles(resolved, proj); - } - else if (reloadLevel === ConfigFileProgramReloadLevel.Partial) { - // Update file names - const result = getFileNamesFromConfigSpecs(proj.configFileSpecs!, getDirectoryPath(resolved), proj.options, parseConfigFileHost); - updateErrorForNoInputFiles(result, resolved, proj.configFileSpecs!, proj.errors, canJsonReportNoInutFiles(proj.raw)); - proj.fileNames = result.fileNames; - watchInputFiles(resolved, proj); - } - - const status = getUpToDateStatus(proj); - verboseReportProjectStatus(resolved, status); - - if (status.type === UpToDateStatusType.UpstreamBlocked) { - if (options.verbose) reportStatus(Diagnostics.Skipping_build_of_project_0_because_its_dependency_1_has_errors, resolved, status.upstreamProjectName); - return; - } - - if (status.type === UpToDateStatusType.UpToDateWithUpstreamTypes) { - // Fake that files have been built by updating output file stamps - updateOutputTimestamps(proj); - return; - } - - const buildResult = needsBuild(status, resolved) ? - buildSingleProject(resolved) : // Actual build - updateBundle(resolved); // Fake that files have been built by manipulating prepend and existing output - if (buildResult & BuildResultFlags.AnyErrors) return; - - const { referencingProjectsMap, buildQueue } = getGlobalDependencyGraph(); - const referencingProjects = referencingProjectsMap.getValue(resolved); - if (!referencingProjects) return; - - // Always use build order to queue projects - for (let index = buildQueue.indexOf(resolved) + 1; index < buildQueue.length; index++) { - const project = buildQueue[index]; - const prepend = referencingProjects.getValue(project); - if (prepend !== undefined) { - // If the project is referenced with prepend, always build downstream projects, - // If declaration output is changed, build the project - // otherwise mark the project UpToDateWithUpstreamTypes so it updates output time stamps - const status = projectStatus.getValue(project); - if (!(buildResult & BuildResultFlags.DeclarationOutputUnchanged)) { - if (status && (status.type === UpToDateStatusType.UpToDate || status.type === UpToDateStatusType.UpToDateWithUpstreamTypes || status.type === UpToDateStatusType.OutOfDateWithPrepend)) { - projectStatus.setValue(project, { - type: UpToDateStatusType.OutOfDateWithUpstream, - outOfDateOutputFileName: status.type === UpToDateStatusType.OutOfDateWithPrepend ? status.outOfDateOutputFileName : status.oldestOutputFileName, - newerProjectName: resolved - }); - } - } - else if (status && status.type === UpToDateStatusType.UpToDate) { - if (prepend) { - projectStatus.setValue(project, { - type: UpToDateStatusType.OutOfDateWithPrepend, - outOfDateOutputFileName: status.oldestOutputFileName, - newerProjectName: resolved - }); - } - else { - status.type = UpToDateStatusType.UpToDateWithUpstreamTypes; - } - } - addProjToQueue(project); - } - } - } - - function createDependencyGraph(roots: ResolvedConfigFileName[]): DependencyGraph { - const temporaryMarks = createFileMap(toPath); - const permanentMarks = createFileMap(toPath); - const circularityReportStack: string[] = []; - const buildOrder: ResolvedConfigFileName[] = []; - const referencingProjectsMap = createFileMap>(toPath); - for (const root of roots) { - visit(root); - } - - return { - buildQueue: buildOrder, - referencingProjectsMap - }; - - function visit(projPath: ResolvedConfigFileName, inCircularContext?: boolean) { - // Already visited - if (permanentMarks.hasKey(projPath)) return; - // Circular - if (temporaryMarks.hasKey(projPath)) { - if (!inCircularContext) { - // TODO:: Do we report this as error? - reportStatus(Diagnostics.Project_references_may_not_form_a_circular_graph_Cycle_detected_Colon_0, circularityReportStack.join("\r\n")); - } - return; - } - - temporaryMarks.setValue(projPath, true); - circularityReportStack.push(projPath); - const parsed = parseConfigFile(projPath); - if (parsed && parsed.projectReferences) { - for (const ref of parsed.projectReferences) { - const resolvedRefPath = resolveProjectName(ref.path); - visit(resolvedRefPath, inCircularContext || ref.circular); - // Get projects referencing resolvedRefPath and add projPath to it - const referencingProjects = getOrCreateValueFromConfigFileMap(referencingProjectsMap, resolvedRefPath, () => createFileMap(toPath)); - referencingProjects.setValue(projPath, !!ref.prepend); - } - } - - circularityReportStack.pop(); - permanentMarks.setValue(projPath, true); - buildOrder.push(projPath); - } - } - - function buildSingleProject(proj: ResolvedConfigFileName): BuildResultFlags { - if (options.dry) { - reportStatus(Diagnostics.A_non_dry_build_would_build_project_0, proj); - return BuildResultFlags.Success; - } - - if (options.verbose) reportStatus(Diagnostics.Building_project_0, proj); - - let resultFlags = BuildResultFlags.DeclarationOutputUnchanged; - - const configFile = parseConfigFile(proj); - if (!configFile) { - // Failed to read the config file - resultFlags |= BuildResultFlags.ConfigFileErrors; - reportParseConfigFileDiagnostic(proj); - projectStatus.setValue(proj, { type: UpToDateStatusType.Unbuildable, reason: "Config file errors" }); - return resultFlags; - } - if (configFile.fileNames.length === 0) { - reportAndStoreErrors(proj, configFile.errors); - // Nothing to build - must be a solution file, basically - return BuildResultFlags.None; - } - - // TODO: handle resolve module name to cache result in project reference redirect - projectCompilerOptions = configFile.options; - // Update module resolution cache if needed - if (moduleResolutionCache) { - const projPath = toPath(proj); - if (moduleResolutionCache.directoryToModuleNameMap.redirectsMap.size === 0) { - // The own map will be for projectCompilerOptions - Debug.assert(moduleResolutionCache.moduleNameToDirectoryMap.redirectsMap.size === 0); - moduleResolutionCache.directoryToModuleNameMap.redirectsMap.set(projPath, moduleResolutionCache.directoryToModuleNameMap.ownMap); - moduleResolutionCache.moduleNameToDirectoryMap.redirectsMap.set(projPath, moduleResolutionCache.moduleNameToDirectoryMap.ownMap); - } - else { - // Set correct own map - Debug.assert(moduleResolutionCache.moduleNameToDirectoryMap.redirectsMap.size > 0); - - const ref: ResolvedProjectReference = { - sourceFile: projectCompilerOptions.configFile!, - commandLine: configFile - }; - moduleResolutionCache.directoryToModuleNameMap.setOwnMap(moduleResolutionCache.directoryToModuleNameMap.getOrCreateMapOfCacheRedirects(ref)); - moduleResolutionCache.moduleNameToDirectoryMap.setOwnMap(moduleResolutionCache.moduleNameToDirectoryMap.getOrCreateMapOfCacheRedirects(ref)); - } - moduleResolutionCache.directoryToModuleNameMap.setOwnOptions(projectCompilerOptions); - moduleResolutionCache.moduleNameToDirectoryMap.setOwnOptions(projectCompilerOptions); - } - - const program = host.createProgram( - configFile.fileNames, - configFile.options, - compilerHost, - getOldProgram(proj, configFile), - configFile.errors, - configFile.projectReferences + function getSyntaxDiagnostics(cancellationToken?: CancellationToken) { + Debug.assertDefined(program); + handleDiagnostics( + [ + ...program!.getConfigFileParsingDiagnostics(), + ...program!.getOptionsDiagnostics(cancellationToken), + ...program!.getGlobalDiagnostics(cancellationToken), + ...program!.getSyntacticDiagnostics(/*sourceFile*/ undefined, cancellationToken) + ], + BuildResultFlags.SyntaxErrors, + "Syntactic" ); + } - // Don't emit anything in the presence of syntactic errors or options diagnostics - const syntaxDiagnostics = [ - ...program.getConfigFileParsingDiagnostics(), - ...program.getOptionsDiagnostics(), - ...program.getGlobalDiagnostics(), - ...program.getSyntacticDiagnostics()]; - if (syntaxDiagnostics.length) { - return buildErrors(syntaxDiagnostics, BuildResultFlags.SyntaxErrors, "Syntactic"); - } - - // Same as above but now for semantic diagnostics - const semanticDiagnostics = program.getSemanticDiagnostics(); - if (semanticDiagnostics.length) { - return buildErrors(semanticDiagnostics, BuildResultFlags.TypeErrors, "Semantic"); - } + function getSemanticDiagnostics(cancellationToken?: CancellationToken) { + handleDiagnostics( + Debug.assertDefined(program).getSemanticDiagnostics(/*sourceFile*/ undefined, cancellationToken), + BuildResultFlags.TypeErrors, + "Semantic" + ); + } + function emit(writeFileCallback?: WriteFileCallback, cancellationToken?: CancellationToken, customTransformers?: CustomTransformers): EmitResult { + Debug.assertDefined(program); + Debug.assert(step === Step.Emit); // Before emitting lets backup state, so we can revert it back if there are declaration errors to handle emit and declaration errors correctly - program.backupState(); - let newestDeclarationFileContentChangedTime = minimumDate; - let anyDtsChanged = false; + program!.backupState(); let declDiagnostics: Diagnostic[] | undefined; const reportDeclarationDiagnostics = (d: Diagnostic) => (declDiagnostics || (declDiagnostics = [])).push(d); const outputFiles: OutputFile[] = []; - emitFilesAndReportErrors(program, reportDeclarationDiagnostics, /*writeFileName*/ undefined, /*reportSummary*/ undefined, (name, text, writeByteOrderMark) => outputFiles.push({ name, text, writeByteOrderMark })); + const { emitResult } = emitFilesAndReportErrors( + program!, + reportDeclarationDiagnostics, + /*writeFileName*/ undefined, + /*reportSummary*/ undefined, + (name, text, writeByteOrderMark) => outputFiles.push({ name, text, writeByteOrderMark }), + cancellationToken, + /*emitOnlyDts*/ false, + customTransformers + ); // Don't emit .d.ts if there are decl file errors if (declDiagnostics) { - program.restoreState(); - return buildErrors(declDiagnostics, BuildResultFlags.DeclarationEmitErrors, "Declaration file"); + program!.restoreState(); + buildResult = buildErrors( + state, + projectPath, + program, + declDiagnostics, + BuildResultFlags.DeclarationEmitErrors, + "Declaration file" + ); + step = Step.QueueReferencingProjects; + return { + emitSkipped: true, + diagnostics: emitResult.diagnostics + }; } // Actual Emit + const { host, compilerHost } = state; + let resultFlags = BuildResultFlags.DeclarationOutputUnchanged; + let newestDeclarationFileContentChangedTime = minimumDate; + let anyDtsChanged = false; const emitterDiagnostics = createDiagnosticCollection(); - const emittedOutputs = createFileMap(toPath as ToPath); + const emittedOutputs = createMap() as FileMap; outputFiles.forEach(({ name, text, writeByteOrderMark }) => { let priorChangeTime: Date | undefined; if (!anyDtsChanged && isDeclarationFile(name)) { // Check for unchanged .d.ts files - if (host.fileExists(name) && readFileWithCache(name) === text) { + if (host.fileExists(name) && state.readFileWithCache(name) === text) { priorChangeTime = host.getModifiedTime(name); } else { @@ -1194,426 +1020,1086 @@ namespace ts { } } - emittedOutputs.setValue(name, name); - writeFile(compilerHost, emitterDiagnostics, name, text, writeByteOrderMark); + emittedOutputs.set(toPath(state, name), name); + writeFile(writeFileCallback ? { writeFile: writeFileCallback } : compilerHost, emitterDiagnostics, name, text, writeByteOrderMark); if (priorChangeTime !== undefined) { newestDeclarationFileContentChangedTime = newer(priorChangeTime, newestDeclarationFileContentChangedTime); - unchangedOutputs.setValue(name, priorChangeTime); } }); + finishEmit( + emitterDiagnostics, + emittedOutputs, + newestDeclarationFileContentChangedTime, + /*newestDeclarationFileContentChangedTimeIsMaximumDate*/ anyDtsChanged, + outputFiles.length ? outputFiles[0].name : getFirstProjectOutput(config, !host.useCaseSensitiveFileNames()), + resultFlags + ); + return emitResult; + } + + function finishEmit( + emitterDiagnostics: DiagnosticCollection, + emittedOutputs: FileMap, + priorNewestUpdateTime: Date, + newestDeclarationFileContentChangedTimeIsMaximumDate: boolean, + oldestOutputFileName: string, + resultFlags: BuildResultFlags + ) { const emitDiagnostics = emitterDiagnostics.getDiagnostics(); if (emitDiagnostics.length) { - return buildErrors(emitDiagnostics, BuildResultFlags.EmitErrors, "Emit"); + buildResult = buildErrors( + state, + projectPath, + program, + emitDiagnostics, + BuildResultFlags.EmitErrors, + "Emit" + ); + step = Step.QueueReferencingProjects; + return emitDiagnostics; } - if (writeFileName) { - emittedOutputs.forEach(name => listEmittedFile(configFile, name)); - listFiles(program, writeFileName); + if (state.writeFileName) { + emittedOutputs.forEach(name => listEmittedFile(state, config, name)); + if (program) listFiles(program, state.writeFileName); } // Update time stamps for rest of the outputs - newestDeclarationFileContentChangedTime = updateOutputTimestampsWorker(configFile, newestDeclarationFileContentChangedTime, Diagnostics.Updating_unchanged_output_timestamps_of_project_0, emittedOutputs); - - const status: Status.UpToDate = { + const newestDeclarationFileContentChangedTime = updateOutputTimestampsWorker(state, config, priorNewestUpdateTime, Diagnostics.Updating_unchanged_output_timestamps_of_project_0, emittedOutputs); + state.diagnostics.delete(projectPath); + state.projectStatus.set(projectPath, { type: UpToDateStatusType.UpToDate, - newestDeclarationFileContentChangedTime: anyDtsChanged ? maximumDate : newestDeclarationFileContentChangedTime, - oldestOutputFileName: outputFiles.length ? outputFiles[0].name : getFirstProjectOutput(configFile, !host.useCaseSensitiveFileNames()) - }; - diagnostics.removeKey(proj); - projectStatus.setValue(proj, status); - afterProgramCreate(proj, program); - projectCompilerOptions = baseCompilerOptions; - return resultFlags; - - function buildErrors(diagnostics: ReadonlyArray, errorFlags: BuildResultFlags, errorType: string) { - resultFlags |= errorFlags; - reportAndStoreErrors(proj, diagnostics); - // List files if any other build error using program (emit errors already report files) - if (writeFileName) listFiles(program, writeFileName); - projectStatus.setValue(proj, { type: UpToDateStatusType.Unbuildable, reason: `${errorType} errors` }); - afterProgramCreate(proj, program); - projectCompilerOptions = baseCompilerOptions; - return resultFlags; - } + newestDeclarationFileContentChangedTime: newestDeclarationFileContentChangedTimeIsMaximumDate ? + maximumDate : + newestDeclarationFileContentChangedTime, + oldestOutputFileName + }); + if (program) afterProgramCreate(state, projectPath, program); + state.projectCompilerOptions = state.baseCompilerOptions; + step = Step.QueueReferencingProjects; + buildResult = resultFlags; + return emitDiagnostics; } - function listEmittedFile(proj: ParsedCommandLine, file: string) { - if (writeFileName && proj.options.listEmittedFiles) { - writeFileName(`TSFILE: ${file}`); - } - } - - function afterProgramCreate(proj: ResolvedConfigFileName, program: T) { - if (host.afterProgramEmitAndDiagnostics) { - host.afterProgramEmitAndDiagnostics(program); - } - if (options.watch) { - program.releaseProgram(); - builderPrograms.setValue(proj, program); - } - } - - function getOldProgram(proj: ResolvedConfigFileName, parsed: ParsedCommandLine) { - if (options.force) return undefined; - const value = builderPrograms.getValue(proj); - if (value) return value; - return readBuilderProgram(parsed.options, readFileWithCache) as any as T; - } - - function updateBundle(proj: ResolvedConfigFileName): BuildResultFlags { - if (options.dry) { - reportStatus(Diagnostics.A_non_dry_build_would_update_output_of_project_0, proj); - return BuildResultFlags.Success; + function emitBundle(writeFileCallback?: WriteFileCallback, customTransformers?: CustomTransformers): EmitResult | BuildInvalidedProject | undefined { + Debug.assert(kind === InvalidatedProjectKind.UpdateBundle); + if (state.options.dry) { + reportStatus(state, Diagnostics.A_non_dry_build_would_update_output_of_project_0, project); + buildResult = BuildResultFlags.Success; + step = Step.QueueReferencingProjects; + return undefined; } - if (options.verbose) reportStatus(Diagnostics.Updating_output_of_project_0, proj); + if (state.options.verbose) reportStatus(state, Diagnostics.Updating_output_of_project_0, project); // Update js, and source map - const config = Debug.assertDefined(parseConfigFile(proj)); - projectCompilerOptions = config.options; + const { compilerHost } = state; + state.projectCompilerOptions = config.options; const outputFiles = emitUsingBuildInfo( config, compilerHost, - ref => parseConfigFile(resolveProjectName(ref.path))); + ref => { + const refName = resolveProjectName(state, ref.path); + return parseConfigFile(state, refName, toResolvedConfigFilePath(state, refName)); + }, + customTransformers + ); + if (isString(outputFiles)) { - reportStatus(Diagnostics.Cannot_update_output_of_project_0_because_there_was_error_reading_file_1, proj, relName(outputFiles)); - return buildSingleProject(proj); + reportStatus(state, Diagnostics.Cannot_update_output_of_project_0_because_there_was_error_reading_file_1, project, relName(state, outputFiles)); + step = Step.BuildInvalidatedProjectOfBundle; + return invalidatedProjectOfBundle = createBuildOrUpdateInvalidedProject( + InvalidatedProjectKind.Build, + state, + project, + projectPath, + projectIndex, + config, + buildOrder + ) as BuildInvalidedProject; } // Actual Emit Debug.assert(!!outputFiles.length); const emitterDiagnostics = createDiagnosticCollection(); - const emittedOutputs = createFileMap(toPath as ToPath); + const emittedOutputs = createMap() as FileMap; outputFiles.forEach(({ name, text, writeByteOrderMark }) => { - emittedOutputs.setValue(name, name); - writeFile(compilerHost, emitterDiagnostics, name, text, writeByteOrderMark); + emittedOutputs.set(toPath(state, name), name); + writeFile(writeFileCallback ? { writeFile: writeFileCallback } : compilerHost, emitterDiagnostics, name, text, writeByteOrderMark); }); - const emitDiagnostics = emitterDiagnostics.getDiagnostics(); - if (emitDiagnostics.length) { - reportAndStoreErrors(proj, emitDiagnostics); - projectStatus.setValue(proj, { type: UpToDateStatusType.Unbuildable, reason: "Emit errors" }); - projectCompilerOptions = baseCompilerOptions; - return BuildResultFlags.DeclarationOutputUnchanged | BuildResultFlags.EmitErrors; - } - if (writeFileName) { - emittedOutputs.forEach(name => listEmittedFile(config, name)); - } - - // Update timestamps for dts - const newestDeclarationFileContentChangedTime = updateOutputTimestampsWorker(config, minimumDate, Diagnostics.Updating_unchanged_output_timestamps_of_project_0, emittedOutputs); - - const status: Status.UpToDate = { - type: UpToDateStatusType.UpToDate, - newestDeclarationFileContentChangedTime, - oldestOutputFileName: outputFiles[0].name - }; - - diagnostics.removeKey(proj); - projectStatus.setValue(proj, status); - projectCompilerOptions = baseCompilerOptions; - return BuildResultFlags.DeclarationOutputUnchanged; + const emitDiagnostics = finishEmit( + emitterDiagnostics, + emittedOutputs, + minimumDate, + /*newestDeclarationFileContentChangedTimeIsMaximumDate*/ false, + outputFiles[0].name, + BuildResultFlags.DeclarationOutputUnchanged + ); + return { emitSkipped: false, diagnostics: emitDiagnostics }; } - function updateOutputTimestamps(proj: ParsedCommandLine) { - if (options.dry) { - return reportStatus(Diagnostics.A_non_dry_build_would_update_timestamps_for_output_of_project_0, proj.options.configFilePath!); - } - const priorNewestUpdateTime = updateOutputTimestampsWorker(proj, minimumDate, Diagnostics.Updating_output_timestamps_of_project_0); - const status: Status.UpToDate = { - type: UpToDateStatusType.UpToDate, - newestDeclarationFileContentChangedTime: priorNewestUpdateTime, - oldestOutputFileName: getFirstProjectOutput(proj, !host.useCaseSensitiveFileNames()) - }; - projectStatus.setValue(proj.options.configFilePath as ResolvedConfigFilePath, status); - } + function executeSteps(till: Step, cancellationToken?: CancellationToken, writeFile?: WriteFileCallback, customTransformers?: CustomTransformers) { + while (step <= till && step < Step.Done) { + const currentStep = step; + switch (step) { + case Step.CreateProgram: + createProgram(); + break; + + case Step.SyntaxDiagnostics: + getSyntaxDiagnostics(cancellationToken); + break; + + case Step.SemanticDiagnostics: + getSemanticDiagnostics(cancellationToken); + break; + + case Step.Emit: + emit(writeFile, cancellationToken, customTransformers); + break; + + case Step.EmitBundle: + emitBundle(writeFile, customTransformers); + break; + + case Step.BuildInvalidatedProjectOfBundle: + Debug.assertDefined(invalidatedProjectOfBundle).done(cancellationToken); + step = Step.Done; + break; + + case Step.QueueReferencingProjects: + queueReferencingProjects(state, project, projectPath, projectIndex, config, buildOrder, Debug.assertDefined(buildResult)); + step++; + break; + + // Should never be done + case Step.Done: + default: + assertType(step); - function updateOutputTimestampsWorker(proj: ParsedCommandLine, priorNewestUpdateTime: Date, verboseMessage: DiagnosticMessage, skipOutputs?: FileMap) { - const outputs = getAllProjectOutputs(proj, !host.useCaseSensitiveFileNames()); - if (!skipOutputs || outputs.length !== skipOutputs.getSize()) { - if (options.verbose) { - reportStatus(verboseMessage, proj.options.configFilePath!); } - const now = host.now ? host.now() : new Date(); - for (const file of outputs) { - if (skipOutputs && skipOutputs.hasKey(file)) { + Debug.assert(step > currentStep); + } + } + } + + function needsBuild({ options }: SolutionBuilderState, status: UpToDateStatus, config: ParsedCommandLine) { + if (status.type !== UpToDateStatusType.OutOfDateWithPrepend || options.force) return true; + return config.fileNames.length === 0 || + !!config.errors.length || + !isIncrementalCompilation(config.options); + } + + function getNextInvalidatedProject( + state: SolutionBuilderState, + buildOrder: readonly ResolvedConfigFileName[], + reportQueue: boolean + ): InvalidatedProject | undefined { + if (!state.projectPendingBuild.size) return undefined; + if (state.currentInvalidatedProject) { + // Only if same buildOrder the currentInvalidated project can be sent again + return arrayIsEqualTo(state.currentInvalidatedProject.buildOrder, buildOrder) ? + state.currentInvalidatedProject : + undefined; + } + + const { options, projectPendingBuild } = state; + for (let projectIndex = 0; projectIndex < buildOrder.length; projectIndex++) { + const project = buildOrder[projectIndex]; + const projectPath = toResolvedConfigFilePath(state, project); + const reloadLevel = state.projectPendingBuild.get(projectPath); + if (reloadLevel === undefined) continue; + + if (reportQueue) { + reportQueue = false; + reportBuildQueue(state, buildOrder); + } + + const config = parseConfigFile(state, project, projectPath); + if (!config) { + reportParseConfigFileDiagnostic(state, projectPath); + projectPendingBuild.delete(projectPath); + continue; + } + + if (reloadLevel === ConfigFileProgramReloadLevel.Full) { + watchConfigFile(state, project, projectPath); + watchWildCardDirectories(state, project, projectPath, config); + watchInputFiles(state, project, projectPath, config); + } + else if (reloadLevel === ConfigFileProgramReloadLevel.Partial) { + // Update file names + const result = getFileNamesFromConfigSpecs(config.configFileSpecs!, getDirectoryPath(project), config.options, state.parseConfigFileHost); + updateErrorForNoInputFiles(result, project, config.configFileSpecs!, config.errors, canJsonReportNoInutFiles(config.raw)); + config.fileNames = result.fileNames; + watchInputFiles(state, project, projectPath, config); + } + + const status = getUpToDateStatus(state, config, projectPath); + verboseReportProjectStatus(state, project, status); + if (!options.force) { + if (status.type === UpToDateStatusType.UpToDate) { + reportAndStoreErrors(state, projectPath, config.errors); + projectPendingBuild.delete(projectPath); + // Up to date, skip + if (options.dry) { + // In a dry build, inform the user of this fact + reportStatus(state, Diagnostics.Project_0_is_up_to_date, project); + } + continue; + } + + if (status.type === UpToDateStatusType.UpToDateWithUpstreamTypes) { + reportAndStoreErrors(state, projectPath, config.errors); + return createUpdateOutputFileStampsProject( + state, + project, + projectPath, + config, + buildOrder + ); + } + } + + if (status.type === UpToDateStatusType.UpstreamBlocked) { + reportAndStoreErrors(state, projectPath, config.errors); + projectPendingBuild.delete(projectPath); + if (options.verbose) reportStatus(state, Diagnostics.Skipping_build_of_project_0_because_its_dependency_1_has_errors, project, status.upstreamProjectName); + continue; + } + + if (status.type === UpToDateStatusType.ContainerOnly) { + reportAndStoreErrors(state, projectPath, config.errors); + projectPendingBuild.delete(projectPath); + // Do nothing + continue; + } + + return createBuildOrUpdateInvalidedProject( + needsBuild(state, status, config) ? + InvalidatedProjectKind.Build : + InvalidatedProjectKind.UpdateBundle, + state, + project, + projectPath, + projectIndex, + config, + buildOrder, + ); + } + + return undefined; + } + + function listEmittedFile({ writeFileName }: SolutionBuilderState, proj: ParsedCommandLine, file: string) { + if (writeFileName && proj.options.listEmittedFiles) { + writeFileName(`TSFILE: ${file}`); + } + } + + function getOldProgram({ options, builderPrograms, compilerHost }: SolutionBuilderState, proj: ResolvedConfigFilePath, parsed: ParsedCommandLine) { + if (options.force) return undefined; + const value = builderPrograms.get(proj); + if (value) return value; + return readBuilderProgram(parsed.options, compilerHost) as any as T; + } + + function afterProgramCreate({ host, watch, builderPrograms }: SolutionBuilderState, proj: ResolvedConfigFilePath, program: T) { + if (host.afterProgramEmitAndDiagnostics) { + host.afterProgramEmitAndDiagnostics(program); + } + if (watch) { + program.releaseProgram(); + builderPrograms.set(proj, program); + } + } + + function buildErrors( + state: SolutionBuilderState, + resolvedPath: ResolvedConfigFilePath, + program: T | undefined, + diagnostics: ReadonlyArray, + errorFlags: BuildResultFlags, + errorType: string + ) { + reportAndStoreErrors(state, resolvedPath, diagnostics); + // List files if any other build error using program (emit errors already report files) + if (program && state.writeFileName) listFiles(program, state.writeFileName); + state.projectStatus.set(resolvedPath, { type: UpToDateStatusType.Unbuildable, reason: `${errorType} errors` }); + if (program) afterProgramCreate(state, resolvedPath, program); + state.projectCompilerOptions = state.baseCompilerOptions; + return errorFlags; + } + + function updateModuleResolutionCache( + state: SolutionBuilderState, + proj: ResolvedConfigFileName, + config: ParsedCommandLine + ) { + if (!state.moduleResolutionCache) return; + + // Update module resolution cache if needed + const { moduleResolutionCache } = state; + const projPath = toPath(state, proj); + if (moduleResolutionCache.directoryToModuleNameMap.redirectsMap.size === 0) { + // The own map will be for projectCompilerOptions + Debug.assert(moduleResolutionCache.moduleNameToDirectoryMap.redirectsMap.size === 0); + moduleResolutionCache.directoryToModuleNameMap.redirectsMap.set(projPath, moduleResolutionCache.directoryToModuleNameMap.ownMap); + moduleResolutionCache.moduleNameToDirectoryMap.redirectsMap.set(projPath, moduleResolutionCache.moduleNameToDirectoryMap.ownMap); + } + else { + // Set correct own map + Debug.assert(moduleResolutionCache.moduleNameToDirectoryMap.redirectsMap.size > 0); + + const ref: ResolvedProjectReference = { + sourceFile: config.options.configFile!, + commandLine: config + }; + moduleResolutionCache.directoryToModuleNameMap.setOwnMap(moduleResolutionCache.directoryToModuleNameMap.getOrCreateMapOfCacheRedirects(ref)); + moduleResolutionCache.moduleNameToDirectoryMap.setOwnMap(moduleResolutionCache.moduleNameToDirectoryMap.getOrCreateMapOfCacheRedirects(ref)); + } + moduleResolutionCache.directoryToModuleNameMap.setOwnOptions(config.options); + moduleResolutionCache.moduleNameToDirectoryMap.setOwnOptions(config.options); + } + + function checkConfigFileUpToDateStatus(state: SolutionBuilderState, configFile: string, oldestOutputFileTime: Date, oldestOutputFileName: string): Status.OutOfDateWithSelf | undefined { + // Check tsconfig time + const tsconfigTime = state.host.getModifiedTime(configFile) || missingFileModifiedTime; + if (oldestOutputFileTime < tsconfigTime) { + return { + type: UpToDateStatusType.OutOfDateWithSelf, + outOfDateOutputFileName: oldestOutputFileName, + newerInputFileName: configFile + }; + } + } + + function getUpToDateStatusWorker(state: SolutionBuilderState, project: ParsedCommandLine, resolvedPath: ResolvedConfigFilePath): UpToDateStatus { + let newestInputFileName: string = undefined!; + let newestInputFileTime = minimumDate; + const { host } = state; + // Get timestamps of input files + for (const inputFile of project.fileNames) { + if (!host.fileExists(inputFile)) { + return { + type: UpToDateStatusType.Unbuildable, + reason: `${inputFile} does not exist` + }; + } + + const inputTime = host.getModifiedTime(inputFile) || missingFileModifiedTime; + if (inputTime > newestInputFileTime) { + newestInputFileName = inputFile; + newestInputFileTime = inputTime; + } + } + + // Container if no files are specified in the project + if (!project.fileNames.length && !canJsonReportNoInutFiles(project.raw)) { + return { + type: UpToDateStatusType.ContainerOnly + }; + } + + // Collect the expected outputs of this project + const outputs = getAllProjectOutputs(project, !host.useCaseSensitiveFileNames()); + + // Now see if all outputs are newer than the newest input + let oldestOutputFileName = "(none)"; + let oldestOutputFileTime = maximumDate; + let newestOutputFileName = "(none)"; + let newestOutputFileTime = minimumDate; + let missingOutputFileName: string | undefined; + let newestDeclarationFileContentChangedTime = minimumDate; + let isOutOfDateWithInputs = false; + for (const output of outputs) { + // Output is missing; can stop checking + // Don't immediately return because we can still be upstream-blocked, which is a higher-priority status + if (!host.fileExists(output)) { + missingOutputFileName = output; + break; + } + + const outputTime = host.getModifiedTime(output) || missingFileModifiedTime; + if (outputTime < oldestOutputFileTime) { + oldestOutputFileTime = outputTime; + oldestOutputFileName = output; + } + + // If an output is older than the newest input, we can stop checking + // Don't immediately return because we can still be upstream-blocked, which is a higher-priority status + if (outputTime < newestInputFileTime) { + isOutOfDateWithInputs = true; + break; + } + + if (outputTime > newestOutputFileTime) { + newestOutputFileTime = outputTime; + newestOutputFileName = output; + } + + // Keep track of when the most recent time a .d.ts file was changed. + // In addition to file timestamps, we also keep track of when a .d.ts file + // had its file touched but not had its contents changed - this allows us + // to skip a downstream typecheck + if (isDeclarationFile(output)) { + const outputModifiedTime = host.getModifiedTime(output) || missingFileModifiedTime; + newestDeclarationFileContentChangedTime = newer(newestDeclarationFileContentChangedTime, outputModifiedTime); + } + } + + let pseudoUpToDate = false; + let usesPrepend = false; + let upstreamChangedProject: string | undefined; + if (project.projectReferences) { + state.projectStatus.set(resolvedPath, { type: UpToDateStatusType.ComputingUpstream }); + for (const ref of project.projectReferences) { + usesPrepend = usesPrepend || !!(ref.prepend); + const resolvedRef = resolveProjectReferencePath(ref); + const resolvedRefPath = toResolvedConfigFilePath(state, resolvedRef); + const refStatus = getUpToDateStatus(state, parseConfigFile(state, resolvedRef, resolvedRefPath), resolvedRefPath); + + // Its a circular reference ignore the status of this project + if (refStatus.type === UpToDateStatusType.ComputingUpstream || + refStatus.type === UpToDateStatusType.ContainerOnly) { // Container only ignore this project + continue; + } + + // An upstream project is blocked + if (refStatus.type === UpToDateStatusType.Unbuildable) { + return { + type: UpToDateStatusType.UpstreamBlocked, + upstreamProjectName: ref.path + }; + } + + // If the upstream project is out of date, then so are we (someone shouldn't have asked, though?) + if (refStatus.type !== UpToDateStatusType.UpToDate) { + return { + type: UpToDateStatusType.UpstreamOutOfDate, + upstreamProjectName: ref.path + }; + } + + // Check oldest output file name only if there is no missing output file name + if (!missingOutputFileName) { + // If the upstream project's newest file is older than our oldest output, we + // can't be out of date because of it + if (refStatus.newestInputFileTime && refStatus.newestInputFileTime <= oldestOutputFileTime) { continue; } - if (isDeclarationFile(file)) { - priorNewestUpdateTime = newer(priorNewestUpdateTime, host.getModifiedTime(file) || missingFileModifiedTime); + // If the upstream project has only change .d.ts files, and we've built + // *after* those files, then we're "psuedo up to date" and eligible for a fast rebuild + if (refStatus.newestDeclarationFileContentChangedTime && refStatus.newestDeclarationFileContentChangedTime <= oldestOutputFileTime) { + pseudoUpToDate = true; + upstreamChangedProject = ref.path; + continue; } - host.setModifiedTime(file, now); - listEmittedFile(proj, file); + // We have an output older than an upstream output - we are out of date + Debug.assert(oldestOutputFileName !== undefined, "Should have an oldest output filename here"); + return { + type: UpToDateStatusType.OutOfDateWithUpstream, + outOfDateOutputFileName: oldestOutputFileName, + newerProjectName: ref.path + }; } } - - return priorNewestUpdateTime; } - function getFilesToClean(): string[] { - // Get the same graph for cleaning we'd use for building - const graph = getGlobalDependencyGraph(); - const filesToDelete: string[] = []; - for (const proj of graph.buildQueue) { - const parsed = parseConfigFile(proj); - if (parsed === undefined) { - // File has gone missing; fine to ignore here - reportParseConfigFileDiagnostic(proj); - continue; - } - const outputs = getAllProjectOutputs(parsed, !host.useCaseSensitiveFileNames()); - for (const output of outputs) { - if (host.fileExists(output)) { - filesToDelete.push(output); - } - } - } - return filesToDelete; - } - - function cleanAllProjects() { - const filesToDelete = getFilesToClean(); - if (options.dry) { - reportStatus(Diagnostics.A_non_dry_build_would_delete_the_following_files_Colon_0, filesToDelete.map(f => `\r\n * ${f}`).join("")); - return ExitStatus.Success; - } - - for (const output of filesToDelete) { - host.deleteFile(output); - } - - return ExitStatus.Success; - } - - function resolveProjectName(name: string): ResolvedConfigFileName { - return resolveConfigFileProjectName(resolvePath(host.getCurrentDirectory(), name)); - } - - function resolveProjectNames(configFileNames: ReadonlyArray): ResolvedConfigFileName[] { - return configFileNames.map(resolveProjectName); - } - - function enableCache() { - if (cacheState) { - disableCache(); - } - - const originalReadFileWithCache = readFileWithCache; - const originalGetSourceFile = compilerHost.getSourceFile; - - const { originalReadFile, originalFileExists, originalDirectoryExists, - originalCreateDirectory, originalWriteFile, getSourceFileWithCache, - readFileWithCache: newReadFileWithCache - } = changeCompilerHostLikeToUseCache(host, toPath, (...args) => originalGetSourceFile.call(compilerHost, ...args)); - readFileWithCache = newReadFileWithCache; - compilerHost.getSourceFile = getSourceFileWithCache!; - - const originalResolveModuleNames = compilerHost.resolveModuleNames; - if (!compilerHost.resolveModuleNames) { - const loader = (moduleName: string, containingFile: string, redirectedReference: ResolvedProjectReference | undefined) => resolveModuleName(moduleName, containingFile, projectCompilerOptions, compilerHost, moduleResolutionCache, redirectedReference).resolvedModule!; - compilerHost.resolveModuleNames = (moduleNames, containingFile, _reusedNames, redirectedReference) => - loadWithLocalCache(Debug.assertEachDefined(moduleNames), containingFile, redirectedReference, loader); - } - - cacheState = { - originalReadFile, - originalFileExists, - originalDirectoryExists, - originalCreateDirectory, - originalWriteFile, - originalReadFileWithCache, - originalGetSourceFile, - originalResolveModuleNames + if (missingOutputFileName !== undefined) { + return { + type: UpToDateStatusType.OutputMissing, + missingOutputFileName }; } - function disableCache() { - if (!cacheState) return; + if (isOutOfDateWithInputs) { + return { + type: UpToDateStatusType.OutOfDateWithSelf, + outOfDateOutputFileName: oldestOutputFileName, + newerInputFileName: newestInputFileName + }; + } + else { + // Check tsconfig time + const configStatus = checkConfigFileUpToDateStatus(state, project.options.configFilePath!, oldestOutputFileTime, oldestOutputFileName); + if (configStatus) return configStatus; - host.readFile = cacheState.originalReadFile; - host.fileExists = cacheState.originalFileExists; - host.directoryExists = cacheState.originalDirectoryExists; - host.createDirectory = cacheState.originalCreateDirectory; - host.writeFile = cacheState.originalWriteFile; - compilerHost.getSourceFile = cacheState.originalGetSourceFile; - readFileWithCache = cacheState.originalReadFileWithCache; - compilerHost.resolveModuleNames = cacheState.originalResolveModuleNames; - extendedConfigCache.clear(); - if (moduleResolutionCache) { - moduleResolutionCache.directoryToModuleNameMap.clear(); - moduleResolutionCache.moduleNameToDirectoryMap.clear(); - } - cacheState = undefined; + // Check extended config time + const extendedConfigStatus = forEach(project.options.configFile!.extendedSourceFiles || emptyArray, configFile => checkConfigFileUpToDateStatus(state, configFile, oldestOutputFileTime, oldestOutputFileName)); + if (extendedConfigStatus) return extendedConfigStatus; } - function buildAllProjects(): ExitStatus { - if (options.watch) { reportWatchStatus(Diagnostics.Starting_compilation_in_watch_mode); } - enableCache(); + if (!state.buildInfoChecked.has(resolvedPath)) { + state.buildInfoChecked.set(resolvedPath, true); + const buildInfoPath = getOutputPathForBuildInfo(project.options); + if (buildInfoPath) { + const value = state.readFileWithCache(buildInfoPath); + const buildInfo = value && getBuildInfo(value); + if (buildInfo && (buildInfo.bundle || buildInfo.program) && buildInfo.version !== version) { + return { + type: UpToDateStatusType.TsVersionOutputOfDate, + version: buildInfo.version + }; + } + } + } - const graph = getGlobalDependencyGraph(); - reportBuildQueue(graph); - let anyFailed = false; - for (const next of graph.buildQueue) { - const proj = parseConfigFile(next); - if (proj === undefined) { - reportParseConfigFileDiagnostic(next); - anyFailed = true; - break; + if (usesPrepend && pseudoUpToDate) { + return { + type: UpToDateStatusType.OutOfDateWithPrepend, + outOfDateOutputFileName: oldestOutputFileName, + newerProjectName: upstreamChangedProject! + }; + } + + // Up to date + return { + type: pseudoUpToDate ? UpToDateStatusType.UpToDateWithUpstreamTypes : UpToDateStatusType.UpToDate, + newestDeclarationFileContentChangedTime, + newestInputFileTime, + newestOutputFileTime, + newestInputFileName, + newestOutputFileName, + oldestOutputFileName + }; + } + + function getUpToDateStatus(state: SolutionBuilderState, project: ParsedCommandLine | undefined, resolvedPath: ResolvedConfigFilePath): UpToDateStatus { + if (project === undefined) { + return { type: UpToDateStatusType.Unbuildable, reason: "File deleted mid-build" }; + } + + const prior = state.projectStatus.get(resolvedPath); + if (prior !== undefined) { + return prior; + } + + const actual = getUpToDateStatusWorker(state, project, resolvedPath); + state.projectStatus.set(resolvedPath, actual); + return actual; + } + + function updateOutputTimestampsWorker(state: SolutionBuilderState, proj: ParsedCommandLine, priorNewestUpdateTime: Date, verboseMessage: DiagnosticMessage, skipOutputs?: FileMap) { + const { host } = state; + const outputs = getAllProjectOutputs(proj, !host.useCaseSensitiveFileNames()); + if (!skipOutputs || outputs.length !== skipOutputs.size) { + let reportVerbose = !!state.options.verbose; + const now = host.now ? host.now() : new Date(); + for (const file of outputs) { + if (skipOutputs && skipOutputs.has(toPath(state, file))) { + continue; } - // report errors early when using continue or break statements - const errors = proj.errors; - const status = getUpToDateStatus(proj); - verboseReportProjectStatus(next, status); + if (reportVerbose) { + reportVerbose = false; + reportStatus(state, verboseMessage, proj.options.configFilePath!); + } - const projName = proj.options.configFilePath!; - if (status.type === UpToDateStatusType.UpToDate && !options.force) { - reportAndStoreErrors(next, errors); - // Up to date, skip - if (defaultOptions.dry) { - // In a dry build, inform the user of this fact - reportStatus(Diagnostics.Project_0_is_up_to_date, projName); + if (isDeclarationFile(file)) { + priorNewestUpdateTime = newer(priorNewestUpdateTime, host.getModifiedTime(file) || missingFileModifiedTime); + } + + host.setModifiedTime(file, now); + listEmittedFile(state, proj, file); + } + } + + return priorNewestUpdateTime; + } + + function updateOutputTimestamps(state: SolutionBuilderState, proj: ParsedCommandLine, resolvedPath: ResolvedConfigFilePath) { + if (state.options.dry) { + return reportStatus(state, Diagnostics.A_non_dry_build_would_update_timestamps_for_output_of_project_0, proj.options.configFilePath!); + } + const priorNewestUpdateTime = updateOutputTimestampsWorker(state, proj, minimumDate, Diagnostics.Updating_output_timestamps_of_project_0); + state.projectStatus.set(resolvedPath, { + type: UpToDateStatusType.UpToDate, + newestDeclarationFileContentChangedTime: priorNewestUpdateTime, + oldestOutputFileName: getFirstProjectOutput(proj, !state.host.useCaseSensitiveFileNames()) + }); + } + + function queueReferencingProjects( + state: SolutionBuilderState, + project: ResolvedConfigFileName, + projectPath: ResolvedConfigFilePath, + projectIndex: number, + config: ParsedCommandLine, + buildOrder: readonly ResolvedConfigFileName[], + buildResult: BuildResultFlags + ) { + // Queue only if there are no errors + if (buildResult & BuildResultFlags.AnyErrors) return; + // Only composite projects can be referenced by other projects + if (!config.options.composite) return; + // Always use build order to queue projects + for (let index = projectIndex + 1; index < buildOrder.length; index++) { + const nextProject = buildOrder[index]; + const nextProjectPath = toResolvedConfigFilePath(state, nextProject); + if (state.projectPendingBuild.has(nextProjectPath)) continue; + + const nextProjectConfig = parseConfigFile(state, nextProject, nextProjectPath); + if (!nextProjectConfig || !nextProjectConfig.projectReferences) continue; + for (const ref of nextProjectConfig.projectReferences) { + const resolvedRefPath = resolveProjectName(state, ref.path); + if (toResolvedConfigFilePath(state, resolvedRefPath) !== projectPath) continue; + // If the project is referenced with prepend, always build downstream projects, + // If declaration output is changed, build the project + // otherwise mark the project UpToDateWithUpstreamTypes so it updates output time stamps + const status = state.projectStatus.get(nextProjectPath); + if (status) { + switch (status.type) { + case UpToDateStatusType.UpToDate: + if (buildResult & BuildResultFlags.DeclarationOutputUnchanged) { + if (ref.prepend) { + state.projectStatus.set(nextProjectPath, { + type: UpToDateStatusType.OutOfDateWithPrepend, + outOfDateOutputFileName: status.oldestOutputFileName, + newerProjectName: project + }); + } + else { + status.type = UpToDateStatusType.UpToDateWithUpstreamTypes; + } + break; + } + + // falls through + case UpToDateStatusType.UpToDateWithUpstreamTypes: + case UpToDateStatusType.OutOfDateWithPrepend: + if (!(buildResult & BuildResultFlags.DeclarationOutputUnchanged)) { + state.projectStatus.set(nextProjectPath, { + type: UpToDateStatusType.OutOfDateWithUpstream, + outOfDateOutputFileName: status.type === UpToDateStatusType.OutOfDateWithPrepend ? status.outOfDateOutputFileName : status.oldestOutputFileName, + newerProjectName: project + }); + } + break; + + case UpToDateStatusType.UpstreamBlocked: + if (toResolvedConfigFilePath(state, resolveProjectName(state, status.upstreamProjectName)) === projectPath) { + clearProjectStatus(state, nextProjectPath); + } + break; } - continue; } - - if (status.type === UpToDateStatusType.UpToDateWithUpstreamTypes && !options.force) { - reportAndStoreErrors(next, errors); - // Fake build - updateOutputTimestamps(proj); - continue; - } - - if (status.type === UpToDateStatusType.UpstreamBlocked) { - reportAndStoreErrors(next, errors); - if (options.verbose) reportStatus(Diagnostics.Skipping_build_of_project_0_because_its_dependency_1_has_errors, projName, status.upstreamProjectName); - continue; - } - - if (status.type === UpToDateStatusType.ContainerOnly) { - reportAndStoreErrors(next, errors); - // Do nothing - continue; - } - - const buildResult = needsBuild(status, next) ? - buildSingleProject(next) : // Actual build - updateBundle(next); // Fake that files have been built by manipulating prepend and existing output - - anyFailed = anyFailed || !!(buildResult & BuildResultFlags.AnyErrors); + addProjToQueue(state, nextProjectPath, ConfigFileProgramReloadLevel.None); + break; } - reportErrorSummary(); - disableCache(); - return anyFailed ? ExitStatus.DiagnosticsPresent_OutputsSkipped : ExitStatus.Success; - } - - function needsBuild(status: UpToDateStatus, configFile: ResolvedConfigFileName) { - if (status.type !== UpToDateStatusType.OutOfDateWithPrepend || options.force) return true; - const config = parseConfigFile(configFile); - return !config || - config.fileNames.length === 0 || - !!config.errors.length || - !isIncrementalCompilation(config.options); - } - - function reportParseConfigFileDiagnostic(proj: ResolvedConfigFileName) { - reportAndStoreErrors(proj, [configFileCache.getValue(proj) as Diagnostic]); - } - - function reportAndStoreErrors(proj: ResolvedConfigFileName, errors: ReadonlyArray) { - reportErrors(errors); - projectErrorsReported.setValue(proj, true); - diagnostics.setValue(proj, errors); - } - - function reportErrors(errors: ReadonlyArray) { - errors.forEach(err => host.reportDiagnostic(err)); - } - - /** - * Report the build ordering inferred from the current project graph if we're in verbose mode - */ - function reportBuildQueue(graph: DependencyGraph) { - if (options.verbose) { - reportStatus(Diagnostics.Projects_in_this_build_Colon_0, graph.buildQueue.map(s => "\r\n * " + relName(s)).join("")); - } - } - - function relName(path: string): string { - return convertToRelativePath(path, host.getCurrentDirectory(), f => compilerHost.getCanonicalFileName(f)); - } - - /** - * Report the up-to-date status of a project if we're in verbose mode - */ - function verboseReportProjectStatus(configFileName: string, status: UpToDateStatus) { - if (!options.verbose) return; - return formatUpToDateStatus(configFileName, status, relName, reportStatus); } } - export function resolveConfigFileProjectName(project: string): ResolvedConfigFileName { - if (fileExtensionIs(project, Extension.Json)) { - return project as ResolvedConfigFileName; + function build(state: SolutionBuilderState, project?: string, cancellationToken?: CancellationToken, onlyReferences?: boolean): ExitStatus { + const buildOrder = getBuildOrderFor(state, project, onlyReferences); + if (!buildOrder) return ExitStatus.InvalidProject_OutputsSkipped; + + setupInitialBuild(state, cancellationToken); + + let reportQueue = true; + let successfulProjects = 0; + let errorProjects = 0; + while (true) { + const invalidatedProject = getNextInvalidatedProject(state, buildOrder, reportQueue); + if (!invalidatedProject) break; + reportQueue = false; + invalidatedProject.done(cancellationToken); + if (state.diagnostics.has(invalidatedProject.projectPath)) { + errorProjects++; + } + else { + successfulProjects++; + } } - return combinePaths(project, "tsconfig.json") as ResolvedConfigFileName; + disableCache(state); + reportErrorSummary(state, buildOrder); + startWatching(state, buildOrder); + + return errorProjects ? + successfulProjects ? + ExitStatus.DiagnosticsPresent_OutputsGenerated : + ExitStatus.DiagnosticsPresent_OutputsSkipped : + ExitStatus.Success; } - export function formatUpToDateStatus(configFileName: string, status: UpToDateStatus, relName: (fileName: string) => string, formatMessage: (message: DiagnosticMessage, ...args: string[]) => T) { + function clean(state: SolutionBuilderState, project?: string, onlyReferences?: boolean) { + const buildOrder = getBuildOrderFor(state, project, onlyReferences); + if (!buildOrder) return ExitStatus.InvalidProject_OutputsSkipped; + + const { options, host } = state; + const filesToDelete = options.dry ? [] as string[] : undefined; + for (const proj of buildOrder) { + const resolvedPath = toResolvedConfigFilePath(state, proj); + const parsed = parseConfigFile(state, proj, resolvedPath); + if (parsed === undefined) { + // File has gone missing; fine to ignore here + reportParseConfigFileDiagnostic(state, resolvedPath); + continue; + } + const outputs = getAllProjectOutputs(parsed, !host.useCaseSensitiveFileNames()); + for (const output of outputs) { + if (host.fileExists(output)) { + if (filesToDelete) { + filesToDelete.push(output); + } + else { + host.deleteFile(output); + invalidateProject(state, resolvedPath, ConfigFileProgramReloadLevel.None); + } + } + } + } + + if (filesToDelete) { + reportStatus(state, Diagnostics.A_non_dry_build_would_delete_the_following_files_Colon_0, filesToDelete.map(f => `\r\n * ${f}`).join("")); + } + + return ExitStatus.Success; + } + + function invalidateProject(state: SolutionBuilderState, resolved: ResolvedConfigFilePath, reloadLevel: ConfigFileProgramReloadLevel) { + // If host implements getParsedCommandLine, we cant get list of files from parseConfigFileHost + if (state.host.getParsedCommandLine && reloadLevel === ConfigFileProgramReloadLevel.Partial) { + reloadLevel = ConfigFileProgramReloadLevel.Full; + } + if (reloadLevel === ConfigFileProgramReloadLevel.Full) { + state.configFileCache.delete(resolved); + state.buildOrder = undefined; + } + state.needsSummary = true; + clearProjectStatus(state, resolved); + addProjToQueue(state, resolved, reloadLevel); + enableCache(state); + } + + function invalidateProjectAndScheduleBuilds(state: SolutionBuilderState, resolvedPath: ResolvedConfigFilePath, reloadLevel: ConfigFileProgramReloadLevel) { + state.reportFileChangeDetected = true; + invalidateProject(state, resolvedPath, reloadLevel); + scheduleBuildInvalidatedProject(state); + } + + function scheduleBuildInvalidatedProject(state: SolutionBuilderState) { + const { hostWithWatch } = state; + if (!hostWithWatch.setTimeout || !hostWithWatch.clearTimeout) { + return; + } + if (state.timerToBuildInvalidatedProject) { + hostWithWatch.clearTimeout(state.timerToBuildInvalidatedProject); + } + state.timerToBuildInvalidatedProject = hostWithWatch.setTimeout(buildNextInvalidatedProject, 250, state); + } + + function buildNextInvalidatedProject(state: SolutionBuilderState) { + state.timerToBuildInvalidatedProject = undefined; + if (state.reportFileChangeDetected) { + state.reportFileChangeDetected = false; + state.projectErrorsReported.clear(); + reportWatchStatus(state, Diagnostics.File_change_detected_Starting_incremental_compilation); + } + const buildOrder = getBuildOrder(state); + const invalidatedProject = getNextInvalidatedProject(state, buildOrder, /*reportQueue*/ false); + if (invalidatedProject) { + invalidatedProject.done(); + if (state.projectPendingBuild.size) { + // Schedule next project for build + if (state.watch && !state.timerToBuildInvalidatedProject) { + scheduleBuildInvalidatedProject(state); + } + return; + } + } + disableCache(state); + reportErrorSummary(state, buildOrder); + } + + function watchConfigFile(state: SolutionBuilderState, resolved: ResolvedConfigFileName, resolvedPath: ResolvedConfigFilePath) { + if (!state.watch || state.allWatchedConfigFiles.has(resolvedPath)) return; + state.allWatchedConfigFiles.set(resolvedPath, state.watchFile( + state.hostWithWatch, + resolved, + () => { + invalidateProjectAndScheduleBuilds(state, resolvedPath, ConfigFileProgramReloadLevel.Full); + }, + PollingInterval.High, + WatchType.ConfigFile, + resolved + )); + } + + function isSameFile(state: SolutionBuilderState, file1: string, file2: string) { + return comparePaths(file1, file2, state.currentDirectory, !state.host.useCaseSensitiveFileNames()) === Comparison.EqualTo; + } + + function isOutputFile(state: SolutionBuilderState, fileName: string, configFile: ParsedCommandLine) { + if (configFile.options.noEmit) return false; + + // ts or tsx files are not output + if (!fileExtensionIs(fileName, Extension.Dts) && + (fileExtensionIs(fileName, Extension.Ts) || fileExtensionIs(fileName, Extension.Tsx))) { + return false; + } + + // If options have --outFile or --out, check if its that + const out = configFile.options.outFile || configFile.options.out; + if (out && (isSameFile(state, fileName, out) || isSameFile(state, fileName, removeFileExtension(out) + Extension.Dts))) { + return true; + } + + // If declarationDir is specified, return if its a file in that directory + if (configFile.options.declarationDir && containsPath(configFile.options.declarationDir, fileName, state.currentDirectory, !state.host.useCaseSensitiveFileNames())) { + return true; + } + + // If --outDir, check if file is in that directory + if (configFile.options.outDir && containsPath(configFile.options.outDir, fileName, state.currentDirectory, !state.host.useCaseSensitiveFileNames())) { + return true; + } + + return !forEach(configFile.fileNames, inputFile => isSameFile(state, fileName, inputFile)); + } + + function watchWildCardDirectories(state: SolutionBuilderState, resolved: ResolvedConfigFileName, resolvedPath: ResolvedConfigFilePath, parsed: ParsedCommandLine) { + if (!state.watch) return; + updateWatchingWildcardDirectories( + getOrCreateValueMapFromConfigFileMap(state.allWatchedWildcardDirectories, resolvedPath), + createMapFromTemplate(parsed.configFileSpecs!.wildcardDirectories), + (dir, flags) => state.watchDirectory( + state.hostWithWatch, + dir, + fileOrDirectory => { + const fileOrDirectoryPath = toPath(state, fileOrDirectory); + if (fileOrDirectoryPath !== toPath(state, dir) && hasExtension(fileOrDirectoryPath) && !isSupportedSourceFileName(fileOrDirectory, parsed.options)) { + state.writeLog(`Project: ${resolved} Detected file add/remove of non supported extension: ${fileOrDirectory}`); + return; + } + + if (isOutputFile(state, fileOrDirectory, parsed)) { + state.writeLog(`${fileOrDirectory} is output file`); + return; + } + + invalidateProjectAndScheduleBuilds(state, resolvedPath, ConfigFileProgramReloadLevel.Partial); + }, + flags, + WatchType.WildcardDirectory, + resolved + ) + ); + } + + function watchInputFiles(state: SolutionBuilderState, resolved: ResolvedConfigFileName, resolvedPath: ResolvedConfigFilePath, parsed: ParsedCommandLine) { + if (!state.watch) return; + mutateMap( + getOrCreateValueMapFromConfigFileMap(state.allWatchedInputFiles, resolvedPath), + arrayToMap(parsed.fileNames, fileName => toPath(state, fileName)), + { + createNewValue: (path, input) => state.watchFilePath( + state.hostWithWatch, + input, + () => invalidateProjectAndScheduleBuilds(state, resolvedPath, ConfigFileProgramReloadLevel.None), + PollingInterval.Low, + path as Path, + WatchType.SourceFile, + resolved + ), + onDeleteValue: closeFileWatcher, + } + ); + } + + function startWatching(state: SolutionBuilderState, buildOrder: readonly ResolvedConfigFileName[]) { + if (!state.watchAllProjectsPending) return; + state.watchAllProjectsPending = false; + for (const resolved of buildOrder) { + const resolvedPath = toResolvedConfigFilePath(state, resolved); + // Watch this file + watchConfigFile(state, resolved, resolvedPath); + + const cfg = parseConfigFile(state, resolved, resolvedPath); + if (cfg) { + // Update watchers for wildcard directories + watchWildCardDirectories(state, resolved, resolvedPath, cfg); + + // Watch input files + watchInputFiles(state, resolved, resolvedPath, cfg); + } + } + } + + /** + * A SolutionBuilder has an immutable set of rootNames that are the "entry point" projects, but + * can dynamically add/remove other projects based on changes on the rootNames' references + */ + function createSolutionBuilderWorker(watch: false, host: SolutionBuilderHost, rootNames: ReadonlyArray, defaultOptions: BuildOptions): SolutionBuilder; + function createSolutionBuilderWorker(watch: true, host: SolutionBuilderWithWatchHost, rootNames: ReadonlyArray, defaultOptions: BuildOptions): SolutionBuilder; + function createSolutionBuilderWorker(watch: boolean, hostOrHostWithWatch: SolutionBuilderHost | SolutionBuilderWithWatchHost, rootNames: ReadonlyArray, options: BuildOptions): SolutionBuilder { + const state = createSolutionBuilderState(watch, hostOrHostWithWatch, rootNames, options); + return { + build: (project, cancellationToken) => build(state, project, cancellationToken), + clean: project => clean(state, project), + buildReferences: (project, cancellationToken) => build(state, project, cancellationToken, /*onlyReferences*/ true), + cleanReferences: project => clean(state, project, /*onlyReferences*/ true), + getNextInvalidatedProject: cancellationToken => { + setupInitialBuild(state, cancellationToken); + return getNextInvalidatedProject(state, getBuildOrder(state), /*reportQueue*/ false); + }, + getBuildOrder: () => getBuildOrder(state), + getUpToDateStatusOfProject: project => { + const configFileName = resolveProjectName(state, project); + const configFilePath = toResolvedConfigFilePath(state, configFileName); + return getUpToDateStatus(state, parseConfigFile(state, configFileName, configFilePath), configFilePath); + }, + invalidateProject: (configFilePath, reloadLevel) => invalidateProject(state, configFilePath, reloadLevel || ConfigFileProgramReloadLevel.None), + buildNextInvalidatedProject: () => buildNextInvalidatedProject(state), + }; + } + + function relName(state: SolutionBuilderState, path: string): string { + return convertToRelativePath(path, state.currentDirectory, f => state.getCanonicalFileName(f)); + } + + function reportStatus(state: SolutionBuilderState, message: DiagnosticMessage, ...args: string[]) { + state.host.reportSolutionBuilderStatus(createCompilerDiagnostic(message, ...args)); + } + + function reportWatchStatus(state: SolutionBuilderState, message: DiagnosticMessage, ...args: (string | number | undefined)[]) { + if (state.hostWithWatch.onWatchStatusChange) { + state.hostWithWatch.onWatchStatusChange(createCompilerDiagnostic(message, ...args), state.host.getNewLine(), state.baseCompilerOptions); + } + } + + function reportErrors({ host }: SolutionBuilderState, errors: ReadonlyArray) { + errors.forEach(err => host.reportDiagnostic(err)); + } + + function reportAndStoreErrors(state: SolutionBuilderState, proj: ResolvedConfigFilePath, errors: ReadonlyArray) { + reportErrors(state, errors); + state.projectErrorsReported.set(proj, true); + if (errors.length) { + state.diagnostics.set(proj, errors); + } + } + + function reportParseConfigFileDiagnostic(state: SolutionBuilderState, proj: ResolvedConfigFilePath) { + reportAndStoreErrors(state, proj, [state.configFileCache.get(proj) as Diagnostic]); + } + + function reportErrorSummary(state: SolutionBuilderState, buildOrder: readonly ResolvedConfigFileName[]) { + if (!state.needsSummary || (!state.watch && !state.host.reportErrorSummary)) return; + state.needsSummary = false; + const { diagnostics } = state; + // Report errors from the other projects + buildOrder.forEach(project => { + const projectPath = toResolvedConfigFilePath(state, project); + if (!state.projectErrorsReported.has(projectPath)) { + reportErrors(state, diagnostics.get(projectPath) || emptyArray); + } + }); + let totalErrors = 0; + diagnostics.forEach(singleProjectErrors => totalErrors += getErrorCountForSummary(singleProjectErrors)); + if (state.watch) { + reportWatchStatus(state, getWatchErrorSummaryDiagnosticMessage(totalErrors), totalErrors); + } + else { + state.host.reportErrorSummary!(totalErrors); + } + } + + /** + * Report the build ordering inferred from the current project graph if we're in verbose mode + */ + function reportBuildQueue(state: SolutionBuilderState, buildQueue: readonly ResolvedConfigFileName[]) { + if (state.options.verbose) { + reportStatus(state, Diagnostics.Projects_in_this_build_Colon_0, buildQueue.map(s => "\r\n * " + relName(state, s)).join("")); + } + } + + function reportUpToDateStatus(state: SolutionBuilderState, configFileName: string, status: UpToDateStatus) { switch (status.type) { case UpToDateStatusType.OutOfDateWithSelf: - return formatMessage(Diagnostics.Project_0_is_out_of_date_because_oldest_output_1_is_older_than_newest_input_2, - relName(configFileName), - relName(status.outOfDateOutputFileName), - relName(status.newerInputFileName)); + return reportStatus( + state, + Diagnostics.Project_0_is_out_of_date_because_oldest_output_1_is_older_than_newest_input_2, + relName(state, configFileName), + relName(state, status.outOfDateOutputFileName), + relName(state, status.newerInputFileName) + ); case UpToDateStatusType.OutOfDateWithUpstream: - return formatMessage(Diagnostics.Project_0_is_out_of_date_because_oldest_output_1_is_older_than_newest_input_2, - relName(configFileName), - relName(status.outOfDateOutputFileName), - relName(status.newerProjectName)); + return reportStatus( + state, + Diagnostics.Project_0_is_out_of_date_because_oldest_output_1_is_older_than_newest_input_2, + relName(state, configFileName), + relName(state, status.outOfDateOutputFileName), + relName(state, status.newerProjectName) + ); case UpToDateStatusType.OutputMissing: - return formatMessage(Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist, - relName(configFileName), - relName(status.missingOutputFileName)); + return reportStatus( + state, + Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist, + relName(state, configFileName), + relName(state, status.missingOutputFileName) + ); case UpToDateStatusType.UpToDate: if (status.newestInputFileTime !== undefined) { - return formatMessage(Diagnostics.Project_0_is_up_to_date_because_newest_input_1_is_older_than_oldest_output_2, - relName(configFileName), - relName(status.newestInputFileName || ""), - relName(status.oldestOutputFileName || "")); + return reportStatus( + state, + Diagnostics.Project_0_is_up_to_date_because_newest_input_1_is_older_than_oldest_output_2, + relName(state, configFileName), + relName(state, status.newestInputFileName || ""), + relName(state, status.oldestOutputFileName || "") + ); } // Don't report anything for "up to date because it was already built" -- too verbose break; case UpToDateStatusType.OutOfDateWithPrepend: - return formatMessage(Diagnostics.Project_0_is_out_of_date_because_output_of_its_dependency_1_has_changed, - relName(configFileName), - relName(status.newerProjectName)); + return reportStatus( + state, + Diagnostics.Project_0_is_out_of_date_because_output_of_its_dependency_1_has_changed, + relName(state, configFileName), + relName(state, status.newerProjectName) + ); case UpToDateStatusType.UpToDateWithUpstreamTypes: - return formatMessage(Diagnostics.Project_0_is_up_to_date_with_d_ts_files_from_its_dependencies, - relName(configFileName)); + return reportStatus( + state, + Diagnostics.Project_0_is_up_to_date_with_d_ts_files_from_its_dependencies, + relName(state, configFileName) + ); case UpToDateStatusType.UpstreamOutOfDate: - return formatMessage(Diagnostics.Project_0_is_out_of_date_because_its_dependency_1_is_out_of_date, - relName(configFileName), - relName(status.upstreamProjectName)); + return reportStatus( + state, + Diagnostics.Project_0_is_out_of_date_because_its_dependency_1_is_out_of_date, + relName(state, configFileName), + relName(state, status.upstreamProjectName) + ); case UpToDateStatusType.UpstreamBlocked: - return formatMessage(Diagnostics.Project_0_can_t_be_built_because_its_dependency_1_has_errors, - relName(configFileName), - relName(status.upstreamProjectName)); + return reportStatus( + state, + Diagnostics.Project_0_can_t_be_built_because_its_dependency_1_has_errors, + relName(state, configFileName), + relName(state, status.upstreamProjectName) + ); case UpToDateStatusType.Unbuildable: - return formatMessage(Diagnostics.Failed_to_parse_file_0_Colon_1, - relName(configFileName), - status.reason); + return reportStatus( + state, + Diagnostics.Failed_to_parse_file_0_Colon_1, + relName(state, configFileName), + status.reason + ); case UpToDateStatusType.TsVersionOutputOfDate: - return formatMessage(Diagnostics.Project_0_is_out_of_date_because_output_for_it_was_generated_with_version_1_that_differs_with_current_version_2, - relName(configFileName), + return reportStatus( + state, + Diagnostics.Project_0_is_out_of_date_because_output_for_it_was_generated_with_version_1_that_differs_with_current_version_2, + relName(state, configFileName), status.version, - version); + version + ); case UpToDateStatusType.ContainerOnly: - // Don't report status on "solution" projects + // Don't report status on "solution" projects case UpToDateStatusType.ComputingUpstream: // Should never leak from getUptoDateStatusWorker break; @@ -1621,4 +2107,13 @@ namespace ts { assertType(status); } } + + /** + * Report the up-to-date status of a project if we're in verbose mode + */ + function verboseReportProjectStatus(state: SolutionBuilderState, configFileName: string, status: UpToDateStatus) { + if (state.options.verbose) { + reportUpToDateStatus(state, configFileName, status); + } + } } diff --git a/src/compiler/tsconfig.json b/src/compiler/tsconfig.json index 18934aa9bb1..f5e16b0d127 100644 --- a/src/compiler/tsconfig.json +++ b/src/compiler/tsconfig.json @@ -8,6 +8,7 @@ "files": [ "core.ts", + "debug.ts", "performance.ts", "semver.ts", @@ -29,6 +30,7 @@ "transformers/utilities.ts", "transformers/destructuring.ts", "transformers/ts.ts", + "transformers/classFields.ts", "transformers/es2017.ts", "transformers/es2018.ts", "transformers/es2019.ts", diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 0e3ab5bec24..7e3483f8434 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2546,6 +2546,8 @@ namespace ts { Shared = 1 << 10, // Referenced as antecedent more than once PreFinally = 1 << 11, // Injected edge that links pre-finally label and pre-try flow AfterFinally = 1 << 12, // Injected edge that links post-finally flow with the rest of the graph + /** @internal */ + Cached = 1 << 13, // Indicates that at least one cross-call cache entry exists for this node, even if not a loop participant Label = BranchLabel | LoopLabel, Condition = TrueCondition | FalseCondition } @@ -2973,7 +2975,7 @@ namespace ts { // For testing purposes only. /* @internal */ structureIsReused?: StructureIsReused; - /* @internal */ getSourceFileFromReference(referencingFile: SourceFile, ref: FileReference): SourceFile | undefined; + /* @internal */ getSourceFileFromReference(referencingFile: SourceFile | UnparsedSource, ref: FileReference): SourceFile | undefined; /* @internal */ getLibFileFromReference(ref: FileReference): SourceFile | undefined; /** Given a source file, get the name of the package it was imported from. */ @@ -3066,6 +3068,9 @@ namespace ts { // Diagnostics were produced and outputs were generated in spite of them. DiagnosticsPresent_OutputsGenerated = 2, + + // When build skipped because passed in project is invalid + InvalidProject_OutputsSkipped = 3, } export interface EmitResult { @@ -3152,6 +3157,7 @@ namespace ts { */ getExportSymbolOfSymbol(symbol: Symbol): Symbol; getPropertySymbolOfDestructuringAssignment(location: Identifier): Symbol | undefined; + getTypeOfAssignmentPattern(pattern: AssignmentPattern): Type; getTypeAtLocation(node: Node): Type; getTypeFromTypeNode(node: TypeNode): Type; @@ -3749,6 +3755,8 @@ namespace ts { extendedContainers?: Symbol[]; // Containers (other than the parent) which this symbol is aliased in extendedContainersByFile?: Map; // Containers (other than the parent) which this symbol is aliased in variances?: VarianceFlags[]; // Alias symbol type argument variance cache + deferralConstituents?: Type[]; // Calculated list of constituents for a deferred type + deferralParent?: Type; // Source union/intersection of a deferred type } /* @internal */ @@ -3775,6 +3783,7 @@ namespace ts { ReverseMapped = 1 << 13, // Property of reverse-inferred homomorphic mapped type OptionalParameter = 1 << 14, // Optional parameter RestParameter = 1 << 15, // Rest parameter + DeferredType = 1 << 16, // Calculation of the type of this symbol is deferred due to processing costs, should be fetched with `getTypeOfSymbolWithDeferredType` Synthetic = SyntheticProperty | SyntheticMethod, Discriminant = HasNonUniformType | HasLiteralType, Partial = ReadPartial | WritePartial @@ -3964,6 +3973,8 @@ namespace ts { StructuredOrInstantiable = StructuredType | Instantiable, /* @internal */ ObjectFlagsType = Nullable | Never | Object | Union | Intersection, + /* @internal */ + Simplifiable = IndexedAccess | Conditional, // 'Narrowable' types are types where narrowing actually narrows. // This *should* be every type other than null, undefined, void, and never Narrowable = Any | Unknown | StructuredOrInstantiable | StringLike | NumberLike | BigIntLike | BooleanLike | ESSymbol | UniqueESSymbol | NonPrimitive, @@ -3972,7 +3983,7 @@ namespace ts { NotPrimitiveUnion = Any | Unknown | Enum | Void | Never | StructuredOrInstantiable, // The following flags are aggregated during union and intersection type construction /* @internal */ - IncludesMask = Any | Unknown | Primitive | Never | Object | Union, + IncludesMask = Any | Unknown | Primitive | Never | Object | Union | NonPrimitive, // The following flags are used for different purposes during union and intersection type construction /* @internal */ IncludesStructuredOrInstantiable = TypeParameter, @@ -4005,6 +4016,8 @@ namespace ts { restrictiveInstantiation?: Type; // Instantiation with type parameters mapped to unconstrained form /* @internal */ immediateBaseConstraint?: Type; // Immediate base constraint cache + /* @internal */ + widened?: Type; // Cached widened form of the type } /* @internal */ @@ -4076,19 +4089,20 @@ namespace ts { MarkerType = 1 << 13, // Marker type used for variance probing JSLiteral = 1 << 14, // Object type declared in JS - disables errors on read/write of nonexisting members FreshLiteral = 1 << 15, // Fresh object literal + ArrayLiteral = 1 << 16, // Originates in an array literal /* @internal */ - PrimitiveUnion = 1 << 16, // Union of only primitive types + PrimitiveUnion = 1 << 17, // Union of only primitive types /* @internal */ - ContainsWideningType = 1 << 17, // Type is or contains undefined or null widening type + ContainsWideningType = 1 << 18, // Type is or contains undefined or null widening type /* @internal */ - ContainsObjectLiteral = 1 << 18, // Type is or contains object literal type + ContainsObjectOrArrayLiteral = 1 << 19, // Type is or contains object literal type /* @internal */ - NonInferrableType = 1 << 19, // Type is or contains anyFunctionType or silentNeverType + NonInferrableType = 1 << 20, // Type is or contains anyFunctionType or silentNeverType ClassOrInterface = Class | Interface, /* @internal */ - RequiresWidening = ContainsWideningType | ContainsObjectLiteral, + RequiresWidening = ContainsWideningType | ContainsObjectOrArrayLiteral, /* @internal */ - PropagatingFlags = ContainsWideningType | ContainsObjectLiteral | NonInferrableType + PropagatingFlags = ContainsWideningType | ContainsObjectOrArrayLiteral | NonInferrableType } /* @internal */ @@ -4141,6 +4155,8 @@ namespace ts { export interface TypeReference extends ObjectType { target: GenericType; // Type reference target typeArguments?: ReadonlyArray; // Type reference type arguments (undefined if none) + /* @internal */ + literalType?: TypeReference; // Clone of type with ObjectFlags.ArrayLiteral set } /* @internal */ @@ -4336,8 +4352,8 @@ namespace ts { root: ConditionalRoot; checkType: Type; extendsType: Type; - trueType: Type; - falseType: Type; + resolvedTrueType: Type; + resolvedFalseType: Type; /* @internal */ resolvedInferredTrueType?: Type; // The `trueType` instantiated with the `combinedMapper`, if present /* @internal */ @@ -4422,15 +4438,16 @@ namespace ts { export type TypeMapper = (t: TypeParameter) => Type; export const enum InferencePriority { - NakedTypeVariable = 1 << 0, // Naked type variable in union or intersection type - HomomorphicMappedType = 1 << 1, // Reverse inference for homomorphic mapped type - MappedTypeConstraint = 1 << 2, // Reverse inference for mapped type - ReturnType = 1 << 3, // Inference made from return type of generic function - LiteralKeyof = 1 << 4, // Inference made from a string literal to a keyof T - NoConstraints = 1 << 5, // Don't infer from constraints of instantiable types - AlwaysStrict = 1 << 6, // Always use strict rules for contravariant inferences + NakedTypeVariable = 1 << 0, // Naked type variable in union or intersection type + HomomorphicMappedType = 1 << 1, // Reverse inference for homomorphic mapped type + PartialHomomorphicMappedType = 1 << 2, // Partial reverse inference for homomorphic mapped type + MappedTypeConstraint = 1 << 3, // Reverse inference for mapped type + ReturnType = 1 << 4, // Inference made from return type of generic function + LiteralKeyof = 1 << 5, // Inference made from a string literal to a keyof T + NoConstraints = 1 << 6, // Don't infer from constraints of instantiable types + AlwaysStrict = 1 << 7, // Always use strict rules for contravariant inferences - PriorityImpliesCombination = ReturnType | MappedTypeConstraint | LiteralKeyof, // These priorities imply that the resulting type should be a combination of all candidates + PriorityImpliesCombination = ReturnType | MappedTypeConstraint | LiteralKeyof, // These priorities imply that the resulting type should be a combination of all candidates } /* @internal */ @@ -5183,6 +5200,7 @@ namespace ts { ContainsYield = 1 << 17, ContainsHoistedDeclarationOrCompletion = 1 << 18, ContainsDynamicImport = 1 << 19, + ContainsClassFields = 1 << 20, // Please leave this as 1 << 29. // It is the maximum bit we can set before we outgrow the size of a v8 small integer (SMI) on an x86 system. @@ -5325,12 +5343,13 @@ namespace ts { Values = 1 << 8, // __values (used by ES2015 for..of and yield* transformations) Read = 1 << 9, // __read (used by ES2015 iterator destructuring transformation) Spread = 1 << 10, // __spread (used by ES2015 array spread and argument list spread transformations) - Await = 1 << 11, // __await (used by ES2017 async generator transformation) - AsyncGenerator = 1 << 12, // __asyncGenerator (used by ES2017 async generator transformation) - AsyncDelegator = 1 << 13, // __asyncDelegator (used by ES2017 async generator yield* transformation) - AsyncValues = 1 << 14, // __asyncValues (used by ES2017 for..await..of transformation) - ExportStar = 1 << 15, // __exportStar (used by CommonJS/AMD/UMD module transformation) - MakeTemplateObject = 1 << 16, // __makeTemplateObject (used for constructing template string array objects) + SpreadArrays = 1 << 11, // __spreadArrays (used by ES2015 array spread and argument list spread transformations) + Await = 1 << 12, // __await (used by ES2017 async generator transformation) + AsyncGenerator = 1 << 13, // __asyncGenerator (used by ES2017 async generator transformation) + AsyncDelegator = 1 << 14, // __asyncDelegator (used by ES2017 async generator yield* transformation) + AsyncValues = 1 << 15, // __asyncValues (used by ES2017 for..await..of transformation) + ExportStar = 1 << 16, // __exportStar (used by CommonJS/AMD/UMD module transformation) + MakeTemplateObject = 1 << 17, // __makeTemplateObject (used for constructing template string array objects) FirstEmitHelper = Extends, LastEmitHelper = MakeTemplateObject, @@ -5380,6 +5399,7 @@ namespace ts { writeFile: WriteFileCallback; getProgramBuildInfo(): ProgramBuildInfo | undefined; + getSourceFileFromReference: Program["getSourceFileFromReference"]; } export interface TransformationContext { @@ -5710,6 +5730,7 @@ namespace ts { /*@internal*/ writeBundleFileInfo?: boolean; /*@internal*/ recordInternalSection?: boolean; /*@internal*/ stripInternal?: boolean; + /*@internal*/ relativeToBuildInfo?: (path: string) => string; } /* @internal */ diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 45dc980296b..562d066b64e 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -2073,7 +2073,7 @@ namespace ts { } export function importFromModuleSpecifier(node: StringLiteralLike): AnyValidImportOrReExport { - return tryGetImportFromModuleSpecifier(node) || Debug.fail(Debug.showSyntaxKind(node.parent)); + return tryGetImportFromModuleSpecifier(node) || Debug.failBadSyntaxKind(node.parent); } export function tryGetImportFromModuleSpecifier(node: StringLiteralLike): AnyValidImportOrReExport | undefined { @@ -2201,13 +2201,13 @@ namespace ts { let result: (JSDoc | JSDocTag)[] | undefined; // Pull parameter comments from declaring function as well if (isVariableLike(hostNode) && hasInitializer(hostNode) && hasJSDocNodes(hostNode.initializer!)) { - result = addRange(result, (hostNode.initializer as HasJSDoc).jsDoc!); + result = append(result, last((hostNode.initializer as HasJSDoc).jsDoc!)); } let node: Node | undefined = hostNode; while (node && node.parent) { if (hasJSDocNodes(node)) { - result = addRange(result, node.jsDoc!); + result = append(result, last(node.jsDoc!)); } if (node.kind === SyntaxKind.Parameter) { @@ -2611,13 +2611,6 @@ namespace ts { return undefined; } - export function tryResolveScriptReference(host: ScriptReferenceHost, sourceFile: SourceFile | UnparsedSource, reference: FileReference) { - if (!host.getCompilerOptions().noResolve) { - const referenceFileName = isRootedDiskPath(reference.fileName) ? reference.fileName : combinePaths(getDirectoryPath(sourceFile.fileName), reference.fileName); - return host.getSourceFile(referenceFileName); - } - } - export function getAncestor(node: Node | undefined, kind: SyntaxKind): Node | undefined { while (node) { if (node.kind === kind) { @@ -2712,11 +2705,19 @@ namespace ts { return isStringLiteralLike(node) || isNumericLiteral(node); } + export function isSignedNumericLiteral(node: Node): node is PrefixUnaryExpression & { operand: NumericLiteral } { + return isPrefixUnaryExpression(node) && (node.operator === SyntaxKind.PlusToken || node.operator === SyntaxKind.MinusToken) && isNumericLiteral(node.operand); + } + /** - * A declaration has a dynamic name if both of the following are true: - * 1. The declaration has a computed property name - * 2. The computed name is *not* expressed as Symbol., where name - * is a property of the Symbol constructor that denotes a built in + * A declaration has a dynamic name if all of the following are true: + * 1. The declaration has a computed property name. + * 2. The computed name is *not* expressed as a StringLiteral. + * 3. The computed name is *not* expressed as a NumericLiteral. + * 4. The computed name is *not* expressed as a PlusToken or MinusToken + * immediately followed by a NumericLiteral. + * 5. The computed name is *not* expressed as `Symbol.`, where `` + * is a property of the Symbol constructor that denotes a built-in * Symbol. */ export function hasDynamicName(declaration: Declaration): declaration is DynamicNamedDeclaration { @@ -2727,6 +2728,7 @@ namespace ts { export function isDynamicName(name: DeclarationName): boolean { return name.kind === SyntaxKind.ComputedPropertyName && !isStringOrNumericLiteralLike(name.expression) && + !isSignedNumericLiteral(name.expression) && !isWellKnownSymbolSyntactically(name.expression); } @@ -4186,78 +4188,6 @@ namespace ts { return getNewLine ? getNewLine() : sys ? sys.newLine : carriageReturnLineFeed; } - /** - * Formats an enum value as a string for debugging and debug assertions. - */ - function formatEnum(value = 0, enumObject: any, isFlags?: boolean) { - const members = getEnumMembers(enumObject); - if (value === 0) { - return members.length > 0 && members[0][0] === 0 ? members[0][1] : "0"; - } - if (isFlags) { - let result = ""; - let remainingFlags = value; - for (let i = members.length - 1; i >= 0 && remainingFlags !== 0; i--) { - const [enumValue, enumName] = members[i]; - if (enumValue !== 0 && (remainingFlags & enumValue) === enumValue) { - remainingFlags &= ~enumValue; - result = `${enumName}${result ? ", " : ""}${result}`; - } - } - if (remainingFlags === 0) { - return result; - } - } - else { - for (const [enumValue, enumName] of members) { - if (enumValue === value) { - return enumName; - } - } - } - return value.toString(); - } - - function getEnumMembers(enumObject: any) { - const result: [number, string][] = []; - for (const name in enumObject) { - const value = enumObject[name]; - if (typeof value === "number") { - result.push([value, name]); - } - } - - return stableSort<[number, string]>(result, (x, y) => compareValues(x[0], y[0])); - } - - export function formatSyntaxKind(kind: SyntaxKind | undefined): string { - return formatEnum(kind, (ts).SyntaxKind, /*isFlags*/ false); - } - - export function formatModifierFlags(flags: ModifierFlags | undefined): string { - return formatEnum(flags, (ts).ModifierFlags, /*isFlags*/ true); - } - - export function formatTransformFlags(flags: TransformFlags | undefined): string { - return formatEnum(flags, (ts).TransformFlags, /*isFlags*/ true); - } - - export function formatEmitFlags(flags: EmitFlags | undefined): string { - return formatEnum(flags, (ts).EmitFlags, /*isFlags*/ true); - } - - export function formatSymbolFlags(flags: SymbolFlags | undefined): string { - return formatEnum(flags, (ts).SymbolFlags, /*isFlags*/ true); - } - - export function formatTypeFlags(flags: TypeFlags | undefined): string { - return formatEnum(flags, (ts).TypeFlags, /*isFlags*/ true); - } - - export function formatObjectFlags(flags: ObjectFlags | undefined): string { - return formatEnum(flags, (ts).ObjectFlags, /*isFlags*/ true); - } - /** * Creates a new TextRange from the provided pos and end. * @@ -5259,6 +5189,9 @@ namespace ts { return node.parent.left.name; } } + else if (isVariableDeclaration(node.parent) && isIdentifier(node.parent.name)) { + return node.parent.name; + } } /** @@ -7646,6 +7579,14 @@ namespace ts { return root + pathComponents.slice(1).join(directorySeparator); } + export function getNormalizedAbsolutePathWithoutRoot(fileName: string, currentDirectory: string | undefined) { + return getPathWithoutRoot(getNormalizedPathComponents(fileName, currentDirectory)); + } + + function getPathWithoutRoot(pathComponents: ReadonlyArray) { + if (pathComponents.length === 0) return ""; + return pathComponents.slice(1).join(directorySeparator); + } } /* @internal */ @@ -8420,29 +8361,6 @@ namespace ts { return pathext ? path.slice(0, path.length - pathext.length) + (startsWith(ext, ".") ? ext : "." + ext) : path; } - export namespace Debug { - export function showSymbol(symbol: Symbol): string { - const symbolFlags = (ts as any).SymbolFlags; - return `{ flags: ${symbolFlags ? showFlags(symbol.flags, symbolFlags) : symbol.flags}; declarations: ${map(symbol.declarations, showSyntaxKind)} }`; - } - - function showFlags(flags: number, flagsEnum: { [flag: number]: string }): string { - const out: string[] = []; - for (let pow = 0; pow <= 30; pow++) { - const n = 1 << pow; - if (flags & n) { - out.push(flagsEnum[n]); - } - } - return out.join("|"); - } - - export function showSyntaxKind(node: Node): string { - const syntaxKind = (ts as any).SyntaxKind; - return syntaxKind ? syntaxKind[node.kind] : node.kind.toString(); - } - } - export function tryParsePattern(pattern: string): Pattern | undefined { // This should be verified outside of here and a proper error thrown. Debug.assert(hasZeroOrOneAsteriskCharacter(pattern)); diff --git a/src/compiler/visitor.ts b/src/compiler/visitor.ts index 0cb3b269105..53ccd81f7a6 100644 --- a/src/compiler/visitor.ts +++ b/src/compiler/visitor.ts @@ -1558,100 +1558,4 @@ namespace ts { function aggregateTransformFlagsForChildNodes(transformFlags: TransformFlags, nodes: NodeArray): TransformFlags { return transformFlags | aggregateTransformFlagsForNodeArray(nodes); } - - export namespace Debug { - let isDebugInfoEnabled = false; - - export function failBadSyntaxKind(node: Node, message?: string): never { - return fail( - `${message || "Unexpected node."}\r\nNode ${formatSyntaxKind(node.kind)} was unexpected.`, - failBadSyntaxKind); - } - - export const assertEachNode = shouldAssert(AssertionLevel.Normal) - ? (nodes: Node[], test: (node: Node) => boolean, message?: string): void => assert( - test === undefined || every(nodes, test), - message || "Unexpected node.", - () => `Node array did not pass test '${getFunctionName(test)}'.`, - assertEachNode) - : noop; - - export const assertNode = shouldAssert(AssertionLevel.Normal) - ? (node: Node | undefined, test: ((node: Node | undefined) => boolean) | undefined, message?: string): void => assert( - test === undefined || test(node), - message || "Unexpected node.", - () => `Node ${formatSyntaxKind(node!.kind)} did not pass test '${getFunctionName(test!)}'.`, - assertNode) - : noop; - - export const assertOptionalNode = shouldAssert(AssertionLevel.Normal) - ? (node: Node, test: (node: Node) => boolean, message?: string): void => assert( - test === undefined || node === undefined || test(node), - message || "Unexpected node.", - () => `Node ${formatSyntaxKind(node.kind)} did not pass test '${getFunctionName(test)}'.`, - assertOptionalNode) - : noop; - - export const assertOptionalToken = shouldAssert(AssertionLevel.Normal) - ? (node: Node, kind: SyntaxKind, message?: string): void => assert( - kind === undefined || node === undefined || node.kind === kind, - message || "Unexpected node.", - () => `Node ${formatSyntaxKind(node.kind)} was not a '${formatSyntaxKind(kind)}' token.`, - assertOptionalToken) - : noop; - - export const assertMissingNode = shouldAssert(AssertionLevel.Normal) - ? (node: Node, message?: string): void => assert( - node === undefined, - message || "Unexpected node.", - () => `Node ${formatSyntaxKind(node.kind)} was unexpected'.`, - assertMissingNode) - : noop; - - /** - * Injects debug information into frequently used types. - */ - export function enableDebugInfo() { - if (isDebugInfoEnabled) return; - - // Add additional properties in debug mode to assist with debugging. - Object.defineProperties(objectAllocator.getSymbolConstructor().prototype, { - __debugFlags: { get(this: Symbol) { return formatSymbolFlags(this.flags); } } - }); - - Object.defineProperties(objectAllocator.getTypeConstructor().prototype, { - __debugFlags: { get(this: Type) { return formatTypeFlags(this.flags); } }, - __debugObjectFlags: { get(this: Type) { return this.flags & TypeFlags.Object ? formatObjectFlags((this).objectFlags) : ""; } }, - __debugTypeToString: { value(this: Type) { return this.checker.typeToString(this); } }, - }); - - const nodeConstructors = [ - objectAllocator.getNodeConstructor(), - objectAllocator.getIdentifierConstructor(), - objectAllocator.getTokenConstructor(), - objectAllocator.getSourceFileConstructor() - ]; - - for (const ctor of nodeConstructors) { - if (!ctor.prototype.hasOwnProperty("__debugKind")) { - Object.defineProperties(ctor.prototype, { - __debugKind: { get(this: Node) { return formatSyntaxKind(this.kind); } }, - __debugModifierFlags: { get(this: Node) { return formatModifierFlags(getModifierFlagsNoCache(this)); } }, - __debugTransformFlags: { get(this: Node) { return formatTransformFlags(this.transformFlags); } }, - __debugEmitFlags: { get(this: Node) { return formatEmitFlags(getEmitFlags(this)); } }, - __debugGetText: { - value(this: Node, includeTrivia?: boolean) { - if (nodeIsSynthesized(this)) return ""; - const parseNode = getParseTreeNode(this); - const sourceFile = parseNode && getSourceFileOfNode(parseNode); - return sourceFile ? getSourceTextOfNodeFromSourceFile(sourceFile, parseNode, includeTrivia) : ""; - } - } - }); - } - } - - isDebugInfoEnabled = true; - } - } } diff --git a/src/compiler/watch.ts b/src/compiler/watch.ts index f9bf2a8468d..6963a4df645 100644 --- a/src/compiler/watch.ts +++ b/src/compiler/watch.ts @@ -88,8 +88,6 @@ namespace ts { return result; } - export type ReportEmitErrorSummary = (errorCount: number) => void; - export function getErrorCountForSummary(diagnostics: ReadonlyArray) { return countWhere(diagnostics, diagnostic => diagnostic.category === DiagnosticCategory.Error); } @@ -113,12 +111,12 @@ namespace ts { getCurrentDirectory(): string; getCompilerOptions(): CompilerOptions; getSourceFiles(): ReadonlyArray; - getSyntacticDiagnostics(): ReadonlyArray; - getOptionsDiagnostics(): ReadonlyArray; - getGlobalDiagnostics(): ReadonlyArray; - getSemanticDiagnostics(): ReadonlyArray; + getSyntacticDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray; + getOptionsDiagnostics(cancellationToken?: CancellationToken): ReadonlyArray; + getGlobalDiagnostics(cancellationToken?: CancellationToken): ReadonlyArray; + getSemanticDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray; getConfigFileParsingDiagnostics(): ReadonlyArray; - emit(targetSourceFile?: SourceFile, writeFile?: WriteFileCallback): EmitResult; + emit(targetSourceFile?: SourceFile, writeFile?: WriteFileCallback, cancellationToken?: CancellationToken, emitOnlyDtsFiles?: boolean, customTransformers?: CustomTransformers): EmitResult; } export function listFiles(program: ProgramToEmitFilesAndReportErrors, writeFileName: (s: string) => void) { @@ -132,25 +130,35 @@ namespace ts { /** * Helper that emit files, report diagnostics and lists emitted and/or source files depending on compiler options */ - export function emitFilesAndReportErrors(program: ProgramToEmitFilesAndReportErrors, reportDiagnostic: DiagnosticReporter, writeFileName?: (s: string) => void, reportSummary?: ReportEmitErrorSummary, writeFile?: WriteFileCallback) { + export function emitFilesAndReportErrors( + program: ProgramToEmitFilesAndReportErrors, + reportDiagnostic: DiagnosticReporter, + writeFileName?: (s: string) => void, + reportSummary?: ReportEmitErrorSummary, + writeFile?: WriteFileCallback, + cancellationToken?: CancellationToken, + emitOnlyDtsFiles?: boolean, + customTransformers?: CustomTransformers + ) { // First get and report any syntactic errors. const diagnostics = program.getConfigFileParsingDiagnostics().slice(); const configFileParsingDiagnosticsLength = diagnostics.length; - addRange(diagnostics, program.getSyntacticDiagnostics()); + addRange(diagnostics, program.getSyntacticDiagnostics(/*sourceFile*/ undefined, cancellationToken)); // If we didn't have any syntactic errors, then also try getting the global and // semantic errors. if (diagnostics.length === configFileParsingDiagnosticsLength) { - addRange(diagnostics, program.getOptionsDiagnostics()); - addRange(diagnostics, program.getGlobalDiagnostics()); + addRange(diagnostics, program.getOptionsDiagnostics(cancellationToken)); + addRange(diagnostics, program.getGlobalDiagnostics(cancellationToken)); if (diagnostics.length === configFileParsingDiagnosticsLength) { - addRange(diagnostics, program.getSemanticDiagnostics()); + addRange(diagnostics, program.getSemanticDiagnostics(/*sourceFile*/ undefined, cancellationToken)); } } // Emit and report any errors we ran into. - const { emittedFiles, emitSkipped, diagnostics: emitDiagnostics } = program.emit(/*targetSourceFile*/ undefined, writeFile); + const emitResult = program.emit(/*targetSourceFile*/ undefined, writeFile, cancellationToken, emitOnlyDtsFiles, customTransformers); + const { emittedFiles, diagnostics: emitDiagnostics } = emitResult; addRange(diagnostics, emitDiagnostics); sortAndDeduplicateDiagnostics(diagnostics).forEach(reportDiagnostic); @@ -167,7 +175,34 @@ namespace ts { reportSummary(getErrorCountForSummary(diagnostics)); } - if (emitSkipped && diagnostics.length > 0) { + return { + emitResult, + diagnostics, + }; + } + + export function emitFilesAndReportErrorsAndGetExitStatus( + program: ProgramToEmitFilesAndReportErrors, + reportDiagnostic: DiagnosticReporter, + writeFileName?: (s: string) => void, + reportSummary?: ReportEmitErrorSummary, + writeFile?: WriteFileCallback, + cancellationToken?: CancellationToken, + emitOnlyDtsFiles?: boolean, + customTransformers?: CustomTransformers + ) { + const { emitResult, diagnostics } = emitFilesAndReportErrors( + program, + reportDiagnostic, + writeFileName, + reportSummary, + writeFile, + cancellationToken, + emitOnlyDtsFiles, + customTransformers + ); + + if (emitResult.emitSkipped && diagnostics.length > 0) { // If the emitter didn't emit anything, then pass that value along. return ExitStatus.DiagnosticsPresent_OutputsSkipped; } @@ -179,7 +214,7 @@ namespace ts { return ExitStatus.Success; } - const noopFileWatcher: FileWatcher = { close: noop }; + export const noopFileWatcher: FileWatcher = { close: noop }; export function createWatchHost(system = sys, reportWatchStatus?: WatchStatusReporter): WatchHost { const onWatchStatusChange = reportWatchStatus || createWatchStatusReporter(system); @@ -375,43 +410,6 @@ namespace ts { return host; } - export function readBuilderProgram(compilerOptions: CompilerOptions, readFile: (path: string) => string | undefined) { - if (compilerOptions.out || compilerOptions.outFile) return undefined; - const buildInfoPath = getOutputPathForBuildInfo(compilerOptions); - if (!buildInfoPath) return undefined; - const content = readFile(buildInfoPath); - if (!content) return undefined; - const buildInfo = getBuildInfo(content); - if (buildInfo.version !== version) return undefined; - if (!buildInfo.program) return undefined; - return createBuildProgramUsingProgramBuildInfo(buildInfo.program); - } - - export function createIncrementalCompilerHost(options: CompilerOptions, system = sys): CompilerHost { - const host = createCompilerHostWorker(options, /*setParentNodes*/ undefined, system); - host.createHash = maybeBind(system, system.createHash); - setGetSourceFileAsHashVersioned(host, system); - changeCompilerHostLikeToUseCache(host, fileName => toPath(fileName, host.getCurrentDirectory(), host.getCanonicalFileName)); - return host; - } - - interface IncrementalProgramOptions { - rootNames: ReadonlyArray; - options: CompilerOptions; - configFileParsingDiagnostics?: ReadonlyArray; - projectReferences?: ReadonlyArray; - host?: CompilerHost; - createProgram?: CreateProgram; - } - function createIncrementalProgram({ - rootNames, options, configFileParsingDiagnostics, projectReferences, host, createProgram - }: IncrementalProgramOptions): T { - host = host || createIncrementalCompilerHost(options); - createProgram = createProgram || createEmitAndSemanticDiagnosticsBuilderProgram as any as CreateProgram; - const oldProgram = readBuilderProgram(options, path => host!.readFile(path)) as any as T; - return createProgram(rootNames, options, host, oldProgram, configFileParsingDiagnostics, projectReferences); - } - export interface IncrementalCompilationOptions { rootNames: ReadonlyArray; options: CompilerOptions; @@ -427,7 +425,7 @@ namespace ts { const system = input.system || sys; const host = input.host || (input.host = createIncrementalCompilerHost(input.options, system)); const builderProgram = createIncrementalProgram(input); - const exitStatus = emitFilesAndReportErrors( + const exitStatus = emitFilesAndReportErrorsAndGetExitStatus( builderProgram, input.reportDiagnostic || createDiagnosticReporter(system), s => host.trace && host.trace(s), @@ -439,6 +437,49 @@ namespace ts { } namespace ts { + export interface ReadBuildProgramHost { + useCaseSensitiveFileNames(): boolean; + getCurrentDirectory(): string; + readFile(fileName: string): string | undefined; + } + export function readBuilderProgram(compilerOptions: CompilerOptions, host: ReadBuildProgramHost) { + if (compilerOptions.out || compilerOptions.outFile) return undefined; + const buildInfoPath = getOutputPathForBuildInfo(compilerOptions); + if (!buildInfoPath) return undefined; + const content = host.readFile(buildInfoPath); + if (!content) return undefined; + const buildInfo = getBuildInfo(content); + if (buildInfo.version !== version) return undefined; + if (!buildInfo.program) return undefined; + return createBuildProgramUsingProgramBuildInfo(buildInfo.program, buildInfoPath, host); + } + + export function createIncrementalCompilerHost(options: CompilerOptions, system = sys): CompilerHost { + const host = createCompilerHostWorker(options, /*setParentNodes*/ undefined, system); + host.createHash = maybeBind(system, system.createHash); + setGetSourceFileAsHashVersioned(host, system); + changeCompilerHostLikeToUseCache(host, fileName => toPath(fileName, host.getCurrentDirectory(), host.getCanonicalFileName)); + return host; + } + + export interface IncrementalProgramOptions { + rootNames: ReadonlyArray; + options: CompilerOptions; + configFileParsingDiagnostics?: ReadonlyArray; + projectReferences?: ReadonlyArray; + host?: CompilerHost; + createProgram?: CreateProgram; + } + + export function createIncrementalProgram({ + rootNames, options, configFileParsingDiagnostics, projectReferences, host, createProgram + }: IncrementalProgramOptions): T { + host = host || createIncrementalCompilerHost(options); + createProgram = createProgram || createEmitAndSemanticDiagnosticsBuilderProgram as any as CreateProgram; + const oldProgram = readBuilderProgram(options, host) as any as T; + return createProgram(rootNames, options, host, oldProgram, configFileParsingDiagnostics, projectReferences); + } + export type WatchStatusReporter = (diagnostic: Diagnostic, newLine: string, options: CompilerOptions) => void; /** Create the program with rootNames and options, if they are undefined, oldProgram and new configFile diagnostics create new program */ export type CreateProgram = (rootNames: ReadonlyArray | undefined, options: CompilerOptions | undefined, host?: CompilerHost, oldProgram?: T, configFileParsingDiagnostics?: ReadonlyArray, projectReferences?: ReadonlyArray | undefined) => T; @@ -567,7 +608,6 @@ namespace ts { /*@internal*/ getCurrentProgram(): T; /** Closes the watch */ - /*@internal*/ close(): void; } @@ -691,6 +731,7 @@ namespace ts { hasChangedAutomaticTypeDirectiveNames = true; scheduleProgramUpdate(); }; + compilerHost.fileIsOpen = returnFalse; compilerHost.maxNumberOfFilesToIterateForInvalidation = host.maxNumberOfFilesToIterateForInvalidation; compilerHost.getCurrentProgram = getCurrentProgram; compilerHost.writeLog = writeLog; @@ -710,7 +751,7 @@ namespace ts { ((typeDirectiveNames, containingFile, redirectedReference) => resolutionCache.resolveTypeReferenceDirectives(typeDirectiveNames, containingFile, redirectedReference)); const userProvidedResolution = !!host.resolveModuleNames || !!host.resolveTypeReferenceDirectives; - builderProgram = readBuilderProgram(compilerOptions, path => compilerHost.readFile(path)) as any as T; + builderProgram = readBuilderProgram(compilerOptions, compilerHost) as any as T; synchronizeProgram(); // Update the wild card directory watch diff --git a/src/harness/client.ts b/src/harness/client.ts index 03d53344616..4f8e84dcdaf 100644 --- a/src/harness/client.ts +++ b/src/harness/client.ts @@ -395,8 +395,15 @@ namespace ts.server { const locations: RenameLocation[] = []; for (const entry of body.locs) { const fileName = entry.file; - for (const { start, end, ...prefixSuffixText } of entry.locs) { - locations.push({ textSpan: this.decodeSpan({ start, end }, fileName), fileName, ...prefixSuffixText }); + for (const { start, end, contextStart, contextEnd, ...prefixSuffixText } of entry.locs) { + locations.push({ + textSpan: this.decodeSpan({ start, end }, fileName), + fileName, + ...(contextStart !== undefined ? + { contextSpan: this.decodeSpan({ start: contextStart, end: contextEnd! }, fileName) } : + undefined), + ...prefixSuffixText + }); } } @@ -424,6 +431,10 @@ namespace ts.server { return renameInfo; } + getSmartSelectionRange() { + return notImplemented(); + } + findRenameLocations(fileName: string, position: number, findInStrings: boolean, findInComments: boolean): RenameLocation[] { if (!this.lastRenameEntry || this.lastRenameEntry.inputs.fileName !== fileName || diff --git a/src/harness/fakes.ts b/src/harness/fakes.ts index 489a41dd677..31e64e2c931 100644 --- a/src/harness/fakes.ts +++ b/src/harness/fakes.ts @@ -388,10 +388,42 @@ namespace fakes { return ts.compareStringsCaseSensitive(ts.isString(a) ? a : a[0], ts.isString(b) ? b : b[0]); } + export function sanitizeBuildInfoProgram(buildInfo: ts.BuildInfo) { + if (buildInfo.program) { + // reference Map + if (buildInfo.program.referencedMap) { + const referencedMap: ts.MapLike = {}; + for (const path of ts.getOwnKeys(buildInfo.program.referencedMap).sort()) { + referencedMap[path] = buildInfo.program.referencedMap[path].sort(); + } + buildInfo.program.referencedMap = referencedMap; + } + + // exportedModulesMap + if (buildInfo.program.exportedModulesMap) { + const exportedModulesMap: ts.MapLike = {}; + for (const path of ts.getOwnKeys(buildInfo.program.exportedModulesMap).sort()) { + exportedModulesMap[path] = buildInfo.program.exportedModulesMap[path].sort(); + } + buildInfo.program.exportedModulesMap = exportedModulesMap; + } + + // semanticDiagnosticsPerFile + if (buildInfo.program.semanticDiagnosticsPerFile) { + buildInfo.program.semanticDiagnosticsPerFile.sort(compareProgramBuildInfoDiagnostic); + } + } + } + export const version = "FakeTSVersion"; export class SolutionBuilderHost extends CompilerHost implements ts.SolutionBuilderHost { - createProgram = ts.createEmitAndSemanticDiagnosticsBuilderProgram; + createProgram: ts.CreateProgram; + + constructor(sys: System | vfs.FileSystem, options?: ts.CompilerOptions, setParentNodes?: boolean, createProgram?: ts.CreateProgram) { + super(sys, options, setParentNodes); + this.createProgram = createProgram || ts.createEmitAndSemanticDiagnosticsBuilderProgram; + } readFile(path: string) { const value = super.readFile(path); @@ -405,30 +437,7 @@ namespace fakes { public writeFile(fileName: string, content: string, writeByteOrderMark: boolean) { if (!ts.isBuildInfoFile(fileName)) return super.writeFile(fileName, content, writeByteOrderMark); const buildInfo = ts.getBuildInfo(content); - if (buildInfo.program) { - // reference Map - if (buildInfo.program.referencedMap) { - const referencedMap: ts.MapLike = {}; - for (const path of ts.getOwnKeys(buildInfo.program.referencedMap).sort()) { - referencedMap[path] = buildInfo.program.referencedMap[path].sort(); - } - buildInfo.program.referencedMap = referencedMap; - } - - // exportedModulesMap - if (buildInfo.program.exportedModulesMap) { - const exportedModulesMap: ts.MapLike = {}; - for (const path of ts.getOwnKeys(buildInfo.program.exportedModulesMap).sort()) { - exportedModulesMap[path] = buildInfo.program.exportedModulesMap[path].sort(); - } - buildInfo.program.exportedModulesMap = exportedModulesMap; - } - - // semanticDiagnosticsPerFile - if (buildInfo.program.semanticDiagnosticsPerFile) { - buildInfo.program.semanticDiagnosticsPerFile.sort(compareProgramBuildInfoDiagnostic); - } - } + sanitizeBuildInfoProgram(buildInfo); buildInfo.version = version; super.writeFile(fileName, ts.getBuildInfoText(buildInfo), writeByteOrderMark); } diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index e8c2912365f..c26b059ced8 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -42,6 +42,7 @@ namespace FourSlash { * is a range with `text in range` "selected". */ ranges: Range[]; + rangesByText?: ts.MultiMap; } export interface Marker { @@ -582,6 +583,21 @@ namespace FourSlash { }); } + public verifyErrorExistsAtRange(range: Range, code: number, expectedMessage?: string) { + const span = ts.createTextSpanFromRange(range); + const hasMatchingError = ts.some( + this.getDiagnostics(range.fileName), + ({ code, messageText, start, length }) => + code === code && + (!expectedMessage || expectedMessage === messageText) && + ts.isNumber(start) && ts.isNumber(length) && + ts.textSpansEqual(span, { start, length })); + + if (!hasMatchingError) { + this.raiseError(`No error with code ${code} found at provided range.`); + } + } + public verifyNumberOfErrorsInCurrentFile(expected: number) { const errors = this.getDiagnostics(this.activeFile.fileName); const actual = errors.length; @@ -798,8 +814,8 @@ namespace FourSlash { } private verifyCompletionEntry(actual: ts.CompletionEntry, expected: FourSlashInterface.ExpectedCompletionEntry) { - const { insertText, replacementSpan, hasAction, isRecommended, kind, kindModifiers, text, documentation, tags, source, sourceDisplay } = typeof expected === "string" - ? { insertText: undefined, replacementSpan: undefined, hasAction: undefined, isRecommended: undefined, kind: undefined, kindModifiers: undefined, text: undefined, documentation: undefined, tags: undefined, source: undefined, sourceDisplay: undefined } + const { insertText, replacementSpan, hasAction, isRecommended, kind, kindModifiers, text, documentation, tags, source, sourceDisplay, sortText } = typeof expected === "string" + ? { insertText: undefined, replacementSpan: undefined, hasAction: undefined, isRecommended: undefined, kind: undefined, kindModifiers: undefined, text: undefined, documentation: undefined, tags: undefined, source: undefined, sourceDisplay: undefined, sortText: undefined } : expected; if (actual.insertText !== insertText) { @@ -825,6 +841,7 @@ namespace FourSlash { assert.equal(actual.hasAction, hasAction); assert.equal(actual.isRecommended, isRecommended); assert.equal(actual.source, source); + assert.equal(actual.sortText, sortText || ts.Completions.SortText.LocationPriority, this.messageAtLastKnownMarker(`Actual entry: ${JSON.stringify(actual)}`)); if (text !== undefined) { const actualDetails = this.getCompletionEntryDetails(actual.name, actual.source)!; @@ -954,12 +971,15 @@ namespace FourSlash { const fullExpected = ts.map(parts, ({ definition, ranges }) => ({ definition: typeof definition === "string" ? definition : { ...definition, range: ts.createTextSpanFromRange(definition.range) }, references: ranges.map(r => { - const { isWriteAccess = false, isDefinition = false, isInString } = (r.marker && r.marker.data || {}) as { isWriteAccess?: boolean, isDefinition?: boolean, isInString?: true }; + const { isWriteAccess = false, isDefinition = false, isInString, contextRangeIndex } = (r.marker && r.marker.data || {}) as { isWriteAccess?: boolean, isDefinition?: boolean, isInString?: true, contextRangeIndex?: number }; return { fileName: r.fileName, textSpan: ts.createTextSpanFromRange(r), isWriteAccess, isDefinition, + ...(contextRangeIndex !== undefined ? + { contextSpan: ts.createTextSpanFromRange(this.getRanges()[contextRangeIndex]) } : + undefined), ...(isInString ? { isInString: true } : undefined), }; }), @@ -996,8 +1016,8 @@ namespace FourSlash { assert.deepEqual | undefined>(refs, expected); } - public verifySingleReferenceGroup(definition: FourSlashInterface.ReferenceGroupDefinition, ranges?: Range[]) { - ranges = ranges || this.getRanges(); + public verifySingleReferenceGroup(definition: FourSlashInterface.ReferenceGroupDefinition, ranges?: Range[] | string) { + ranges = ts.isString(ranges) ? this.rangesByText().get(ranges)! : ranges || this.getRanges(); this.verifyReferenceGroups(ranges, [{ definition, ranges }]); } @@ -1010,7 +1030,7 @@ Actual: ${stringify(fullActual)}`); }; if ((actual === undefined) !== (expected === undefined)) { - fail(`Expected ${expected}, got ${actual}`); + fail(`Expected ${stringify(expected)}, got ${stringify(actual)}`); } for (const key in actual) { @@ -1020,7 +1040,7 @@ Actual: ${stringify(fullActual)}`); recur(ak, ek, path ? path + "." + key : key); } else if (ak !== ek) { - fail(`Expected '${key}' to be '${ek}', got '${ak}'`); + fail(`Expected '${key}' to be '${stringify(ek)}', got '${stringify(ak)}'`); } } } @@ -1172,7 +1192,9 @@ Actual: ${stringify(fullActual)}`); public verifyRenameLocations(startRanges: ArrayOrSingle, options: FourSlashInterface.RenameLocationsOptions) { const { findInStrings = false, findInComments = false, ranges = this.getRanges(), providePrefixAndSuffixTextForRename = true } = ts.isArray(options) ? { findInStrings: false, findInComments: false, ranges: options, providePrefixAndSuffixTextForRename: true } : options; - for (const startRange of toArray(startRanges)) { + const _startRanges = toArray(startRanges); + assert(_startRanges.length); + for (const startRange of _startRanges) { this.goToRangeStart(startRange); const renameInfo = this.languageService.getRenameInfo(this.activeFile.fileName, this.currentCaretPosition); @@ -1188,7 +1210,15 @@ Actual: ${stringify(fullActual)}`); locations && ts.sort(locations, (r1, r2) => ts.compareStringsCaseSensitive(r1.fileName, r2.fileName) || r1.textSpan.start - r2.textSpan.start); assert.deepEqual(sort(references), sort(ranges.map((rangeOrOptions): ts.RenameLocation => { const { range, ...prefixSuffixText } = "range" in rangeOrOptions ? rangeOrOptions : { range: rangeOrOptions }; - return { fileName: range.fileName, textSpan: ts.createTextSpanFromRange(range), ...prefixSuffixText }; + const { contextRangeIndex } = (range.marker && range.marker.data || {}) as { contextRangeIndex?: number; }; + return { + fileName: range.fileName, + textSpan: ts.createTextSpanFromRange(range), + ...(contextRangeIndex !== undefined ? + { contextSpan: ts.createTextSpanFromRange(this.getRanges()[contextRangeIndex]) } : + undefined), + ...prefixSuffixText + }; }))); } } @@ -1207,7 +1237,7 @@ Actual: ${stringify(fullActual)}`); } } - public verifySignatureHelpPresence(expectPresent: boolean, triggerReason: ts.SignatureHelpTriggerReason | undefined, markers: ReadonlyArray) { + public verifySignatureHelpPresence(expectPresent: boolean, triggerReason: ts.SignatureHelpTriggerReason | undefined, markers: ReadonlyArray) { if (markers.length) { for (const marker of markers) { this.goToMarker(marker); @@ -1417,12 +1447,7 @@ Actual: ${stringify(fullActual)}`); } public baselineCurrentFileBreakpointLocations() { - let baselineFile = this.testData.globalOptions[MetadataOptionNames.baselineFile]; - if (!baselineFile) { - baselineFile = this.activeFile.fileName.replace(this.basePath + "/breakpointValidation", "bpSpan"); - baselineFile = baselineFile.replace(ts.Extension.Ts, ".baseline"); - - } + const baselineFile = this.getBaselineFileName().replace("breakpointValidation", "bpSpan"); Harness.Baseline.runBaseline(baselineFile, this.baselineCurrentFileLocations(pos => this.getBreakpointStatementLocation(pos)!)); } @@ -1497,8 +1522,7 @@ Actual: ${stringify(fullActual)}`); } public baselineQuickInfo() { - const baselineFile = this.testData.globalOptions[MetadataOptionNames.baselineFile] || - ts.getBaseFileName(this.activeFile.fileName).replace(ts.Extension.Ts, ".baseline"); + const baselineFile = this.getBaselineFileName(); Harness.Baseline.runBaseline( baselineFile, stringify( @@ -1508,6 +1532,39 @@ Actual: ${stringify(fullActual)}`); })))); } + public baselineSmartSelection() { + const n = "\n"; + const baselineFile = this.getBaselineFileName(); + const markers = this.getMarkers(); + const fileContent = this.activeFile.content; + const text = markers.map(marker => { + const baselineContent = [fileContent.slice(0, marker.position) + "/**/" + fileContent.slice(marker.position) + n]; + let selectionRange: ts.SelectionRange | undefined = this.languageService.getSmartSelectionRange(this.activeFile.fileName, marker.position); + while (selectionRange) { + const { textSpan } = selectionRange; + let masked = Array.from(fileContent).map((char, index) => { + const charCode = char.charCodeAt(0); + if (index >= textSpan.start && index < ts.textSpanEnd(textSpan)) { + return char === " " ? "•" : ts.isLineBreak(charCode) ? `↲${n}` : char; + } + return ts.isLineBreak(charCode) ? char : " "; + }).join(""); + masked = masked.replace(/^\s*$\r?\n?/gm, ""); // Remove blank lines + const isRealCharacter = (char: string) => char !== "•" && char !== "↲" && !ts.isWhiteSpaceLike(char.charCodeAt(0)); + const leadingWidth = Array.from(masked).findIndex(isRealCharacter); + const trailingWidth = ts.findLastIndex(Array.from(masked), isRealCharacter); + masked = masked.slice(0, leadingWidth) + + masked.slice(leadingWidth, trailingWidth).replace(/•/g, " ").replace(/↲/g, "") + + masked.slice(trailingWidth); + baselineContent.push(masked); + selectionRange = selectionRange.parent; + } + return baselineContent.join(fileContent.includes("\n") ? n + n : n); + }).join(n.repeat(2) + "=".repeat(80) + n.repeat(2)); + + Harness.Baseline.runBaseline(baselineFile, text); + } + public printBreakpointLocation(pos: number) { Harness.IO.log("\n**Pos: " + pos + " " + this.spanInfoToString(this.getBreakpointStatementLocation(pos)!, " ")); } @@ -1562,6 +1619,11 @@ Actual: ${stringify(fullActual)}`); Harness.IO.log(stringify(help.items[help.selectedItemIndex])); } + private getBaselineFileName() { + return this.testData.globalOptions[MetadataOptionNames.baselineFile] || + ts.getBaseFileName(this.activeFile.fileName).replace(ts.Extension.Ts, ".baseline"); + } + private getSignatureHelp({ triggerReason }: FourSlashInterface.VerifySignatureHelpOptions): ts.SignatureHelpItems | undefined { return this.languageService.getSignatureHelpItems(this.activeFile.fileName, this.currentCaretPosition, { triggerReason @@ -1811,6 +1873,7 @@ Actual: ${stringify(fullActual)}`); range.end = updatePosition(range.end, editStart, editEnd, newText); } } + this.testData.rangesByText = undefined; } private removeWhitespace(text: string): string { @@ -1993,7 +2056,9 @@ Actual: ${stringify(fullActual)}`); } public rangesByText(): ts.Map { + if (this.testData.rangesByText) return this.testData.rangesByText; const result = ts.createMultiMap(); + this.testData.rangesByText = result; for (const range of this.getRanges()) { const text = this.rangeText(range); result.add(text, range); @@ -2205,6 +2270,20 @@ Actual: ${stringify(fullActual)}`); }); } + public verifyOutliningHintSpans(spans: Range[]) { + const actual = this.languageService.getOutliningSpans(this.activeFile.fileName); + + if (actual.length !== spans.length) { + this.raiseError(`verifyOutliningHintSpans failed - expected total spans to be ${spans.length}, but was ${actual.length}`); + } + + ts.zipWith(spans, actual, (expectedSpan, actualSpan, i) => { + if (expectedSpan.pos !== actualSpan.hintSpan.start || expectedSpan.end !== ts.textSpanEnd(actualSpan.hintSpan)) { + return this.raiseError(`verifyOutliningSpans failed - span ${(i + 1)} expected: (${expectedSpan.pos},${expectedSpan.end}), actual: (${actualSpan.hintSpan.start},${ts.textSpanEnd(actualSpan.hintSpan)})`); + } + }); + } + public verifyTodoComments(descriptors: string[], spans: Range[]) { const actual = this.languageService.getTodoComments(this.activeFile.fileName, descriptors.map(d => { return { text: d, priority: 0 }; })); @@ -2667,8 +2746,9 @@ Actual: ${stringify(fullActual)}`); return this.languageService.getDocumentHighlights(this.activeFile.fileName, this.currentCaretPosition, filesToSearch); } - public verifyRangesAreOccurrences(isWriteAccess?: boolean) { - const ranges = this.getRanges(); + public verifyRangesAreOccurrences(isWriteAccess?: boolean, ranges?: Range[]) { + ranges = ranges || this.getRanges(); + assert(ranges.length); for (const r of ranges) { this.goToRangeStart(r); this.verifyOccurrencesAtPositionListCount(ranges.length); @@ -2678,8 +2758,13 @@ Actual: ${stringify(fullActual)}`); } } - public verifyRangesWithSameTextAreRenameLocations() { - this.rangesByText().forEach(ranges => this.verifyRangesAreRenameLocations(ranges)); + public verifyRangesWithSameTextAreRenameLocations(...texts: string[]) { + if (texts.length) { + texts.forEach(text => this.verifyRangesAreRenameLocations(this.rangesByText().get(text)!)); + } + else { + this.rangesByText().forEach(ranges => this.verifyRangesAreRenameLocations(ranges)); + } } public verifyRangesWithSameTextAreDocumentHighlights() { @@ -2694,6 +2779,7 @@ Actual: ${stringify(fullActual)}`); public verifyRangesAreDocumentHighlights(ranges: Range[] | undefined, options: FourSlashInterface.VerifyDocumentHighlightsOptions | undefined) { ranges = ranges || this.getRanges(); + assert(ranges.length); const fileNames = options && options.filesToSearch || unique(ranges, range => range.fileName); for (const range of ranges) { this.goToRangeStart(range); @@ -2863,7 +2949,9 @@ Actual: ${stringify(fullActual)}`); } public noMoveToNewFile() { - for (const range of this.getRanges()) { + const ranges = this.getRanges(); + assert(ranges.length); + for (const range of ranges) { for (const refactor of this.getApplicableRefactors(range, { allowTextChangesInNewFiles: true })) { if (refactor.name === "Move to a new file") { ts.Debug.fail("Did not expect to get 'move to a new file' refactor"); @@ -3735,15 +3823,15 @@ namespace FourSlashInterface { assert(ranges.length !== 0, "Array of ranges is expected to be non-empty"); } - public noSignatureHelp(...markers: string[]): void { + public noSignatureHelp(...markers: (string | FourSlash.Marker)[]): void { this.state.verifySignatureHelpPresence(/*expectPresent*/ false, /*triggerReason*/ undefined, markers); } - public noSignatureHelpForTriggerReason(reason: ts.SignatureHelpTriggerReason, ...markers: string[]): void { + public noSignatureHelpForTriggerReason(reason: ts.SignatureHelpTriggerReason, ...markers: (string | FourSlash.Marker)[]): void { this.state.verifySignatureHelpPresence(/*expectPresent*/ false, reason, markers); } - public signatureHelpPresentForTriggerReason(reason: ts.SignatureHelpTriggerReason, ...markers: string[]): void { + public signatureHelpPresentForTriggerReason(reason: ts.SignatureHelpTriggerReason, ...markers: (string | FourSlash.Marker)[]): void { this.state.verifySignatureHelpPresence(/*expectPresent*/ true, reason, markers); } @@ -3924,7 +4012,7 @@ namespace FourSlashInterface { this.state.verifyGetReferencesForServerTest(expected); } - public singleReferenceGroup(definition: ReferenceGroupDefinition, ranges?: FourSlash.Range[]) { + public singleReferenceGroup(definition: ReferenceGroupDefinition, ranges?: FourSlash.Range[] | string) { this.state.verifySingleReferenceGroup(definition, ranges); } @@ -3936,6 +4024,10 @@ namespace FourSlashInterface { this.state.verifyNoErrors(); } + public errorExistsAtRange(range: FourSlash.Range, code: number, message?: string) { + this.state.verifyErrorExistsAtRange(range, code, message); + } + public numberOfErrorsInCurrentFile(expected: number) { this.state.verifyNumberOfErrorsInCurrentFile(expected); } @@ -3960,6 +4052,10 @@ namespace FourSlashInterface { this.state.baselineQuickInfo(); } + public baselineSmartSelection() { + this.state.baselineSmartSelection(); + } + public nameOrDottedNameSpanTextIs(text: string) { this.state.verifyCurrentNameOrDottedNameSpanText(text); } @@ -3968,6 +4064,10 @@ namespace FourSlashInterface { this.state.verifyOutliningSpans(spans, kind); } + public outliningHintSpansInCurrentFile(spans: FourSlash.Range[]) { + this.state.verifyOutliningHintSpans(spans); + } + public todoCommentsInCurrentFile(descriptors: string[]) { this.state.verifyTodoComments(descriptors, this.state.getRanges()); } @@ -4038,12 +4138,12 @@ namespace FourSlashInterface { this.state.verifyOccurrencesAtPositionListCount(expectedCount); } - public rangesAreOccurrences(isWriteAccess?: boolean) { - this.state.verifyRangesAreOccurrences(isWriteAccess); + public rangesAreOccurrences(isWriteAccess?: boolean, ranges?: FourSlash.Range[]) { + this.state.verifyRangesAreOccurrences(isWriteAccess, ranges); } - public rangesWithSameTextAreRenameLocations() { - this.state.verifyRangesWithSameTextAreRenameLocations(); + public rangesWithSameTextAreRenameLocations(...texts: string[]) { + this.state.verifyRangesWithSameTextAreRenameLocations(...texts); } public rangesAreRenameLocations(options?: FourSlash.Range[] | { findInStrings?: boolean, findInComments?: boolean, ranges?: FourSlash.Range[] }) { @@ -4398,18 +4498,63 @@ namespace FourSlashInterface { } } export namespace Completion { - const functionEntry = (name: string): ExpectedCompletionEntryObject => ({ name, kind: "function", kindModifiers: "declare" }); - const varEntry = (name: string): ExpectedCompletionEntryObject => ({ name, kind: "var", kindModifiers: "declare" }); - const moduleEntry = (name: string): ExpectedCompletionEntryObject => ({ name, kind: "module", kindModifiers: "declare" }); - const keywordEntry = (name: string): ExpectedCompletionEntryObject => ({ name, kind: "keyword" }); - const methodEntry = (name: string): ExpectedCompletionEntryObject => ({ name, kind: "method", kindModifiers: "declare" }); - const propertyEntry = (name: string): ExpectedCompletionEntryObject => ({ name, kind: "property", kindModifiers: "declare" }); - const interfaceEntry = (name: string): ExpectedCompletionEntryObject => ({ name, kind: "interface", kindModifiers: "declare" }); - const typeEntry = (name: string): ExpectedCompletionEntryObject => ({ name, kind: "type", kindModifiers: "declare" }); + export import SortText = ts.Completions.SortText; + + const functionEntry = (name: string): ExpectedCompletionEntryObject => ({ + name, + kind: "function", + kindModifiers: "declare", + sortText: SortText.GlobalsOrKeywords + }); + const varEntry = (name: string): ExpectedCompletionEntryObject => ({ + name, + kind: "var", + kindModifiers: "declare", + sortText: SortText.GlobalsOrKeywords + }); + const moduleEntry = (name: string): ExpectedCompletionEntryObject => ({ + name, + kind: "module", + kindModifiers: "declare", + sortText: SortText.GlobalsOrKeywords + }); + const keywordEntry = (name: string): ExpectedCompletionEntryObject => ({ + name, + kind: "keyword", + sortText: SortText.GlobalsOrKeywords + }); + const methodEntry = (name: string): ExpectedCompletionEntryObject => ({ + name, + kind: "method", + kindModifiers: "declare", + sortText: SortText.LocationPriority + }); + const propertyEntry = (name: string): ExpectedCompletionEntryObject => ({ + name, + kind: "property", + kindModifiers: "declare", + sortText: SortText.LocationPriority + }); + const interfaceEntry = (name: string): ExpectedCompletionEntryObject => ({ + name, + kind: "interface", + kindModifiers: "declare", + sortText: SortText.GlobalsOrKeywords + }); + const typeEntry = (name: string): ExpectedCompletionEntryObject => ({ + name, + kind: "type", + kindModifiers: "declare", + sortText: SortText.GlobalsOrKeywords + }); const res: ExpectedCompletionEntryObject[] = []; for (let i = ts.SyntaxKind.FirstKeyword; i <= ts.SyntaxKind.LastKeyword; i++) { - res.push({ name: ts.Debug.assertDefined(ts.tokenToString(i)), kind: "keyword" }); + res.push({ + name: ts.Debug.assertDefined(ts.tokenToString(i)), + kind: "keyword", + sortText: SortText.GlobalsOrKeywords + }); } export const keywordsWithUndefined: ReadonlyArray = res; export const keywords: ReadonlyArray = keywordsWithUndefined.filter(k => k.name !== "undefined"); @@ -4516,11 +4661,15 @@ namespace FourSlashInterface { moduleEntry("Intl"), ]; + export const globalThisEntry: ExpectedCompletionEntry = { + name: "globalThis", + kind: "module", + sortText: SortText.GlobalsOrKeywords + }; export const globalTypes = globalTypesPlus([]); - export function globalTypesPlus(plus: ReadonlyArray): ReadonlyArray { return [ - { name: "globalThis", kind: "module" }, + globalThisEntry, ...globalTypeDecls, ...plus, ...typeKeywords, @@ -4569,7 +4718,11 @@ namespace FourSlashInterface { export const classElementInJsKeywords = getInJsKeywords(classElementKeywords); export const constructorParameterKeywords: ReadonlyArray = - ["private", "protected", "public", "readonly"].map((name): ExpectedCompletionEntryObject => ({ name, kind: "keyword" })); + ["private", "protected", "public", "readonly"].map((name): ExpectedCompletionEntryObject => ({ + name, + kind: "keyword", + sortText: SortText.GlobalsOrKeywords + })); export const functionMembers: ReadonlyArray = [ methodEntry("apply"), @@ -4798,13 +4951,18 @@ namespace FourSlashInterface { "await", ].map(keywordEntry); + export const undefinedVarEntry: ExpectedCompletionEntry = { + name: "undefined", + kind: "var", + sortText: SortText.GlobalsOrKeywords + }; // TODO: many of these are inappropriate to always provide export const globalsInsideFunction = (plus: ReadonlyArray): ReadonlyArray => [ { name: "arguments", kind: "local var" }, ...plus, - { name: "globalThis", kind: "module" }, + globalThisEntry, ...globalsVars, - { name: "undefined", kind: "var" }, + undefinedVarEntry, ...globalKeywordsInsideFunction, ]; @@ -4813,10 +4971,10 @@ namespace FourSlashInterface { // TODO: many of these are inappropriate to always provide export const globalsInJsInsideFunction = (plus: ReadonlyArray): ReadonlyArray => [ { name: "arguments", kind: "local var" }, - { name: "globalThis", kind: "module" }, + globalThisEntry, ...globalsVars, ...plus, - { name: "undefined", kind: "var" }, + undefinedVarEntry, ...globalInJsKeywordsInsideFunction, ]; @@ -4954,34 +5112,34 @@ namespace FourSlashInterface { })(); export const globals: ReadonlyArray = [ - { name: "globalThis", kind: "module" }, + globalThisEntry, ...globalsVars, - { name: "undefined", kind: "var" }, + undefinedVarEntry, ...globalKeywords ]; export const globalsInJs: ReadonlyArray = [ - { name: "globalThis", kind: "module" }, + globalThisEntry, ...globalsVars, - { name: "undefined", kind: "var" }, + undefinedVarEntry, ...globalInJsKeywords ]; export function globalsPlus(plus: ReadonlyArray): ReadonlyArray { return [ - { name: "globalThis", kind: "module" }, + globalThisEntry, ...globalsVars, ...plus, - { name: "undefined", kind: "var" }, + undefinedVarEntry, ...globalKeywords]; } export function globalsInJsPlus(plus: ReadonlyArray): ReadonlyArray { return [ - { name: "globalThis", kind: "module" }, + globalThisEntry, ...globalsVars, ...plus, - { name: "undefined", kind: "var" }, + undefinedVarEntry, ...globalInJsKeywords]; } } @@ -5014,6 +5172,7 @@ namespace FourSlashInterface { readonly documentation?: string; readonly sourceDisplay?: string; readonly tags?: ReadonlyArray; + readonly sortText?: ts.Completions.SortText; } export interface VerifyCompletionsOptions { @@ -5028,7 +5187,7 @@ namespace FourSlashInterface { } export interface VerifySignatureHelpOptions { - readonly marker?: ArrayOrSingle; + readonly marker?: ArrayOrSingle; /** @default 1 */ readonly overloadsCount?: number; /** @default undefined */ diff --git a/src/harness/harness.ts b/src/harness/harness.ts index f03103d0239..fe5ec344322 100644 --- a/src/harness/harness.ts +++ b/src/harness/harness.ts @@ -501,7 +501,7 @@ namespace Harness { } function enumerateTestFiles(runner: RunnerBase) { - return runner.enumerateTestFiles(); + return runner.getTestFiles(); } function listFiles(path: string, spec: RegExp, options: { recursive?: boolean } = {}) { @@ -1311,6 +1311,13 @@ namespace Harness { if (jsCode.length && jsCode.charCodeAt(jsCode.length - 1) !== ts.CharacterCodes.lineFeed) { jsCode += "\r\n"; } + if (!result.diagnostics.length && !ts.endsWith(file.file, ts.Extension.Json)) { + const fileParseResult = ts.createSourceFile(file.file, file.text, options.target || ts.ScriptTarget.ES3, /*parentNodes*/ false, ts.endsWith(file.file, "x") ? ts.ScriptKind.JSX : ts.ScriptKind.JS); + if (ts.length(fileParseResult.parseDiagnostics)) { + jsCode += getErrorBaseline([file.asTestFile()], fileParseResult.parseDiagnostics); + return; + } + } jsCode += fileOutput(file, harnessSettings); }); diff --git a/src/harness/harnessLanguageService.ts b/src/harness/harnessLanguageService.ts index b70afecb791..03ccbb5d6de 100644 --- a/src/harness/harnessLanguageService.ts +++ b/src/harness/harnessLanguageService.ts @@ -472,6 +472,9 @@ namespace Harness.LanguageService { getRenameInfo(fileName: string, position: number, options?: ts.RenameInfoOptions): ts.RenameInfo { return unwrapJSONCallResult(this.shim.getRenameInfo(fileName, position, options)); } + getSmartSelectionRange(fileName: string, position: number): ts.SelectionRange { + return unwrapJSONCallResult(this.shim.getSmartSelectionRange(fileName, position)); + } findRenameLocations(fileName: string, position: number, findInStrings: boolean, findInComments: boolean, providePrefixAndSuffixTextForRename?: boolean): ts.RenameLocation[] { return unwrapJSONCallResult(this.shim.findRenameLocations(fileName, position, findInStrings, findInComments, providePrefixAndSuffixTextForRename)); } diff --git a/src/harness/runnerbase.ts b/src/harness/runnerbase.ts index b29ebb7f920..70045c54c80 100644 --- a/src/harness/runnerbase.ts +++ b/src/harness/runnerbase.ts @@ -1,7 +1,10 @@ -type TestRunnerKind = CompilerTestKind | FourslashTestKind | "project" | "rwc" | "test262" | "user" | "dt"; +type TestRunnerKind = CompilerTestKind | FourslashTestKind | "project" | "rwc" | "test262" | "user" | "dt" | "docker"; type CompilerTestKind = "conformance" | "compiler"; type FourslashTestKind = "fourslash" | "fourslash-shims" | "fourslash-shims-pp" | "fourslash-server"; +let shards = 1; +let shardId = 1; + abstract class RunnerBase { // contains the tests to run public tests: (string | Harness.FileBasedTest)[] = []; @@ -19,6 +22,14 @@ abstract class RunnerBase { abstract enumerateTestFiles(): (string | Harness.FileBasedTest)[]; + getTestFiles(): ReturnType { + const all = this.enumerateTestFiles(); + if (shards === 1) { + return all as ReturnType; + } + return all.filter((_val, idx) => idx % shards === (shardId - 1)) as ReturnType; + } + /** The working directory where tests are found. Needed for batch testing where the input path will differ from the output path inside baselines */ public workingDirectory = ""; diff --git a/src/jsTyping/jsTyping.ts b/src/jsTyping/jsTyping.ts index 379ca6491a6..c2b2ad3f5b3 100644 --- a/src/jsTyping/jsTyping.ts +++ b/src/jsTyping/jsTyping.ts @@ -70,6 +70,10 @@ namespace ts.JsTyping { export const nodeCoreModules = arrayToSet(nodeCoreModuleList); + export function nonRelativeModuleNameForTypingCache(moduleName: string) { + return nodeCoreModules.has(moduleName) ? "node" : moduleName; + } + /** * A map of loose file names to library names that we are confident require typings */ @@ -150,7 +154,7 @@ namespace ts.JsTyping { // add typings for unresolved imports if (unresolvedImports) { const module = deduplicate( - unresolvedImports.map(moduleId => nodeCoreModules.has(moduleId) ? "node" : moduleId), + unresolvedImports.map(nonRelativeModuleNameForTypingCache), equateStringsCaseSensitive, compareStringsCaseSensitive); addInferredTypings(module, "Inferred typings from unresolved imports"); diff --git a/src/lib/dom.generated.d.ts b/src/lib/dom.generated.d.ts index b1daca71fed..e527838fc76 100644 --- a/src/lib/dom.generated.d.ts +++ b/src/lib/dom.generated.d.ts @@ -250,6 +250,15 @@ interface ConvolverOptions extends AudioNodeOptions { disableNormalization?: boolean; } +interface CredentialCreationOptions { + signal?: AbortSignal; +} + +interface CredentialRequestOptions { + mediation?: CredentialMediationRequirement; + signal?: AbortSignal; +} + interface CustomEventInit extends EventInit { detail?: T; } @@ -432,7 +441,6 @@ interface EventModifierInit extends UIEventInit { modifierFnLock?: boolean; modifierHyper?: boolean; modifierNumLock?: boolean; - modifierOS?: boolean; modifierScrollLock?: boolean; modifierSuper?: boolean; modifierSymbol?: boolean; @@ -546,6 +554,12 @@ interface ImageEncodeOptions { type?: string; } +interface InputEventInit extends UIEventInit { + data?: string | null; + inputType?: string; + isComposing?: boolean; +} + interface IntersectionObserverEntryInit { boundingClientRect: DOMRectInit; intersectionRatio: number; @@ -589,6 +603,7 @@ interface KeyAlgorithm { interface KeyboardEventInit extends EventModifierInit { code?: string; + isComposing?: boolean; key?: string; location?: number; repeat?: boolean; @@ -780,12 +795,33 @@ interface MultiCacheQueryOptions extends CacheQueryOptions { } interface MutationObserverInit { + /** + * Set to a list of attribute local names (without namespace) if not all attribute mutations need to be observed and attributes is true or omitted. + */ attributeFilter?: string[]; + /** + * Set to true if attributes is true or omitted and target's attribute value before the mutation needs to be recorded. + */ attributeOldValue?: boolean; + /** + * Set to true if mutations to target's attributes are to be observed. Can be omitted if attributeOldValue or attributeFilter is specified. + */ attributes?: boolean; + /** + * Set to true if mutations to target's data are to be observed. Can be omitted if characterDataOldValue is specified. + */ characterData?: boolean; + /** + * Set to true if characterData is set to true or omitted and target's data before the mutation needs to be recorded. + */ characterDataOldValue?: boolean; + /** + * Set to true if mutations to target's children are to be observed. + */ childList?: boolean; + /** + * Set to true if mutations to not just target, but also target's descendants are to be observed. + */ subtree?: boolean; } @@ -1390,18 +1426,57 @@ interface RegistrationOptions { } interface RequestInit { + /** + * A BodyInit object or null to set request's body. + */ body?: BodyInit | null; + /** + * A string indicating how the request will interact with the browser's cache to set request's cache. + */ cache?: RequestCache; + /** + * A string indicating whether credentials will be sent with the request always, never, or only when sent to a same-origin URL. Sets request's credentials. + */ credentials?: RequestCredentials; + /** + * A Headers object, an object literal, or an array of two-item arrays to set request's headers. + */ headers?: HeadersInit; + /** + * A cryptographic hash of the resource to be fetched by request. Sets request's integrity. + */ integrity?: string; + /** + * A boolean to set request's keepalive. + */ keepalive?: boolean; + /** + * A string to set request's method. + */ method?: string; + /** + * A string to indicate whether the request will use CORS, or will be restricted to same-origin URLs. Sets request's mode. + */ mode?: RequestMode; + /** + * A string indicating whether request follows redirects, results in an error upon encountering a redirect, or returns the redirect (in an opaque fashion). Sets request's redirect. + */ redirect?: RequestRedirect; + /** + * A string whose value is a same-origin URL, "about:client", or the empty string, to set request's referrer. + */ referrer?: string; + /** + * A referrer policy to set request's referrerPolicy. + */ referrerPolicy?: ReferrerPolicy; + /** + * An AbortSignal to set request's signal. + */ signal?: AbortSignal | null; + /** + * Can only be null. Used to disassociate request from any Window. + */ window?: any; } @@ -1654,14 +1729,15 @@ interface WebAuthnExtensions { } interface WebGLContextAttributes { - alpha?: GLboolean; - antialias?: GLboolean; - depth?: GLboolean; + alpha?: boolean; + antialias?: boolean; + depth?: boolean; + desynchronized?: boolean; failIfMajorPerformanceCaveat?: boolean; powerPreference?: WebGLPowerPreference; - premultipliedAlpha?: GLboolean; - preserveDrawingBuffer?: GLboolean; - stencil?: GLboolean; + premultipliedAlpha?: boolean; + preserveDrawingBuffer?: boolean; + stencil?: boolean; } interface WebGLContextEventInit extends EventInit { @@ -1706,8 +1782,7 @@ interface AbortController { */ readonly signal: AbortSignal; /** - * Invoking this method will set this object's AbortSignal's aborted flag and - * signal to any observers that the associated activity is to be aborted. + * Invoking this method will set this object's AbortSignal's aborted flag and signal to any observers that the associated activity is to be aborted. */ abort(): void; } @@ -1724,8 +1799,7 @@ interface AbortSignalEventMap { /** A signal object that allows you to communicate with a DOM request (such as a Fetch) and abort it if required via an AbortController object. */ interface AbortSignal extends EventTarget { /** - * Returns true if this AbortSignal's AbortController has signaled to abort, and false - * otherwise. + * Returns true if this AbortSignal's AbortController has signaled to abort, and false otherwise. */ readonly aborted: boolean; onabort: ((this: AbortSignal, ev: Event) => any) | null; @@ -1741,10 +1815,25 @@ declare var AbortSignal: { }; interface AbstractRange { + /** + * Returns true if range is collapsed, and false otherwise. + */ readonly collapsed: boolean; + /** + * Returns range's end node. + */ readonly endContainer: Node; + /** + * Returns range's end offset. + */ readonly endOffset: number; + /** + * Returns range's start node. + */ readonly startContainer: Node; + /** + * Returns range's start offset. + */ readonly startOffset: number; } @@ -1854,6 +1943,11 @@ declare var AnimationEvent: { new(type: string, animationEventInitDict?: AnimationEventInit): AnimationEvent; }; +interface AnimationFrameProvider { + cancelAnimationFrame(handle: number): void; + requestAnimationFrame(callback: FrameRequestCallback): number; +} + interface AnimationPlaybackEvent extends Event { readonly currentTime: number | null; readonly timelineTime: number | null; @@ -2343,7 +2437,7 @@ declare var BroadcastChannel: { new(name: string): BroadcastChannel; }; -/** An interface of the Streams API provides a built-in byte length queuing strategy that can be used when constructing streams. */ +/** This Streams API interface provides a built-in byte length queuing strategy that can be used when constructing streams. */ interface ByteLengthQueuingStrategy extends QueuingStrategy { highWaterMark: number; size(chunk: ArrayBufferView): number; @@ -2522,9 +2616,9 @@ declare var CSSRuleList: { /** An object that is a CSS declaration block, and exposes style information and various style-related methods and properties. */ interface CSSStyleDeclaration { - alignContent: string | null; - alignItems: string | null; - alignSelf: string | null; + alignContent: string; + alignItems: string; + alignSelf: string; alignmentBaseline: string | null; animation: string; animationDelay: string; @@ -2594,7 +2688,7 @@ interface CSSStyleDeclaration { clipPath: string | null; clipRule: string | null; color: string | null; - colorInterpolationFilters: string | null; + colorInterpolationFilters: string; columnCount: string; columnFill: string; columnGap: string; @@ -2611,7 +2705,7 @@ interface CSSStyleDeclaration { cssFloat: string | null; cssText: string; cursor: string; - direction: string | null; + direction: string; display: string | null; dominantBaseline: string | null; emptyCells: string | null; @@ -2619,7 +2713,7 @@ interface CSSStyleDeclaration { fill: string | null; fillOpacity: string | null; fillRule: string | null; - filter: string | null; + filter: string; flex: string | null; flexBasis: string | null; flexDirection: string | null; @@ -2627,20 +2721,27 @@ interface CSSStyleDeclaration { flexGrow: string | null; flexShrink: string | null; flexWrap: string | null; - floodColor: string | null; - floodOpacity: string | null; - font: string | null; - fontFamily: string | null; - fontFeatureSettings: string | null; - fontSize: string | null; - fontSizeAdjust: string | null; - fontStretch: string | null; - fontStyle: string | null; - fontVariant: string | null; - fontWeight: string | null; - gap: string | null; + floodColor: string; + floodOpacity: string; + font: string; + fontFamily: string; + fontFeatureSettings: string; + fontKerning: string; + fontSize: string; + fontSizeAdjust: string; + fontStretch: string; + fontStyle: string; + fontSynthesis: string; + fontVariant: string; + fontVariantCaps: string; + fontVariantEastAsian: string; + fontVariantLigatures: string; + fontVariantNumeric: string; + fontVariantPosition: string; + fontWeight: string; + gap: string; glyphOrientationHorizontal: string | null; - glyphOrientationVertical: string | null; + glyphOrientationVertical: string; grid: string | null; gridArea: string | null; gridAutoColumns: string | null; @@ -2648,24 +2749,25 @@ interface CSSStyleDeclaration { gridAutoRows: string | null; gridColumn: string | null; gridColumnEnd: string | null; - gridColumnGap: string | null; + gridColumnGap: string; gridColumnStart: string | null; - gridGap: string | null; + gridGap: string; gridRow: string | null; gridRowEnd: string | null; - gridRowGap: string | null; + gridRowGap: string; gridRowStart: string | null; gridTemplate: string | null; gridTemplateAreas: string | null; gridTemplateColumns: string | null; gridTemplateRows: string | null; height: string | null; + hyphens: string; imageOrientation: string; imageRendering: string; imeMode: string | null; - justifyContent: string | null; - justifyItems: string | null; - justifySelf: string | null; + justifyContent: string; + justifyItems: string; + justifySelf: string; kerning: string | null; layoutGrid: string | null; layoutGridChar: string | null; @@ -2674,9 +2776,9 @@ interface CSSStyleDeclaration { layoutGridType: string | null; left: string | null; readonly length: number; - letterSpacing: string | null; - lightingColor: string | null; - lineBreak: string | null; + letterSpacing: string; + lightingColor: string; + lineBreak: string; lineHeight: string | null; listStyle: string | null; listStyleImage: string | null; @@ -2755,6 +2857,8 @@ interface CSSStyleDeclaration { outlineStyle: string; outlineWidth: string; overflow: string | null; + overflowAnchor: string; + overflowWrap: string; overflowX: string | null; overflowY: string | null; padding: string | null; @@ -2769,13 +2873,16 @@ interface CSSStyleDeclaration { penAction: string | null; perspective: string | null; perspectiveOrigin: string | null; + placeContent: string; + placeItems: string; + placeSelf: string; pointerEvents: string | null; position: string | null; quotes: string | null; - resize: string | null; + resize: string; right: string | null; rotate: string | null; - rowGap: string | null; + rowGap: string; rubyAlign: string | null; rubyOverhang: string | null; rubyPosition: string | null; @@ -2791,24 +2898,34 @@ interface CSSStyleDeclaration { strokeMiterlimit: string | null; strokeOpacity: string | null; strokeWidth: string | null; + tabSize: string; tableLayout: string | null; - textAlign: string | null; - textAlignLast: string | null; + textAlign: string; + textAlignLast: string; textAnchor: string | null; - textCombineUpright: string | null; - textDecoration: string | null; - textIndent: string | null; - textJustify: string | null; + textCombineUpright: string; + textDecoration: string; + textDecorationColor: string; + textDecorationLine: string; + textDecorationStyle: string; + textEmphasis: string; + textEmphasisColor: string; + textEmphasisPosition: string; + textEmphasisStyle: string; + textIndent: string; + textJustify: string; textKashida: string | null; textKashidaSpace: string | null; + textOrientation: string; textOverflow: string; - textShadow: string | null; - textTransform: string | null; - textUnderlinePosition: string | null; + textShadow: string; + textTransform: string; + textUnderlinePosition: string; top: string | null; touchAction: string; - transform: string | null; - transformOrigin: string | null; + transform: string; + transformBox: string; + transformOrigin: string; transformStyle: string | null; transition: string; transitionDelay: string; @@ -2816,8 +2933,8 @@ interface CSSStyleDeclaration { transitionProperty: string; transitionTimingFunction: string; translate: string | null; - unicodeBidi: string | null; - userSelect: string | null; + unicodeBidi: string; + userSelect: string; verticalAlign: string | null; visibility: string | null; /** @deprecated */ @@ -2973,14 +3090,14 @@ interface CSSStyleDeclaration { webkitUserModify: string | null; webkitUserSelect: string | null; webkitWritingMode: string | null; - whiteSpace: string | null; + whiteSpace: string; widows: string | null; width: string | null; willChange: string; - wordBreak: string | null; - wordSpacing: string | null; - wordWrap: string | null; - writingMode: string | null; + wordBreak: string; + wordSpacing: string; + wordWrap: string; + writingMode: string; zIndex: string | null; zoom: string | null; getPropertyPriority(propertyName: string): string; @@ -3124,11 +3241,9 @@ interface CanvasFilters { /** An opaque object describing a gradient. It is returned by the methods CanvasRenderingContext2D.createLinearGradient() or CanvasRenderingContext2D.createRadialGradient(). */ interface CanvasGradient { /** - * Adds a color stop with the given color to the gradient at the given offset. 0.0 is the offset - * at one end of the gradient, 1.0 is the offset at the other end. - * Throws an "IndexSizeError" DOMException if the offset - * is out of range. Throws a "SyntaxError" DOMException if - * the color cannot be parsed. + * Adds a color stop with the given color to the gradient at the given offset. 0.0 is the offset at one end of the gradient, 1.0 is the offset at the other end. + * + * Throws an "IndexSizeError" DOMException if the offset is out of range. Throws a "SyntaxError" DOMException if the color cannot be parsed. */ addColorStop(offset: number, color: string): void; } @@ -3176,8 +3291,7 @@ interface CanvasPathDrawingStyles { /** An opaque object describing a pattern, based on an image, a canvas, or a video, created by the CanvasRenderingContext2D.createPattern() method. */ interface CanvasPattern { /** - * Sets the transformation matrix that will be used when rendering the pattern during a fill or - * stroke painting operation. + * Sets the transformation matrix that will be used when rendering the pattern during a fill or stroke painting operation. */ setTransform(transform?: DOMMatrix2DInit): void; } @@ -3294,14 +3408,14 @@ declare var CharacterData: { interface ChildNode extends Node { /** * Inserts nodes just after node, while replacing strings in nodes with equivalent Text nodes. - * Throws a "HierarchyRequestError" DOMException if the constraints of - * the node tree are violated. + * + * Throws a "HierarchyRequestError" DOMException if the constraints of the node tree are violated. */ after(...nodes: (Node | string)[]): void; /** * Inserts nodes just before node, while replacing strings in nodes with equivalent Text nodes. - * Throws a "HierarchyRequestError" DOMException if the constraints of - * the node tree are violated. + * + * Throws a "HierarchyRequestError" DOMException if the constraints of the node tree are violated. */ before(...nodes: (Node | string)[]): void; /** @@ -3310,8 +3424,8 @@ interface ChildNode extends Node { remove(): void; /** * Replaces node with nodes, while replacing strings in nodes with equivalent Text nodes. - * Throws a "HierarchyRequestError" DOMException if the constraints of - * the node tree are violated. + * + * Throws a "HierarchyRequestError" DOMException if the constraints of the node tree are violated. */ replaceWith(...nodes: (Node | string)[]): void; } @@ -3387,13 +3501,11 @@ declare var Comment: { /** The DOM CompositionEvent represents events that occur due to the user indirectly entering text. */ interface CompositionEvent extends UIEvent { readonly data: string; - readonly locale: string; - initCompositionEvent(typeArg: string, canBubbleArg: boolean, cancelableArg: boolean, viewArg: Window, dataArg: string, locale: string): void; } declare var CompositionEvent: { prototype: CompositionEvent; - new(typeArg: string, eventInitDict?: CompositionEventInit): CompositionEvent; + new(type: string, eventInitDict?: CompositionEventInit): CompositionEvent; }; interface ConcatParams extends Algorithm { @@ -3474,7 +3586,7 @@ interface Coordinates { readonly speed: number | null; } -/** An interface of the Streams API provides a built-in byte length queuing strategy that can be used when constructing streams. */ +/** This Streams API interface provides a built-in byte length queuing strategy that can be used when constructing streams. */ interface CountQueuingStrategy extends QueuingStrategy { highWaterMark: number; size(chunk: any): 1; @@ -3485,6 +3597,28 @@ declare var CountQueuingStrategy: { new(options: { highWaterMark: number }): CountQueuingStrategy; }; +interface Credential { + readonly id: string; + readonly type: string; +} + +declare var Credential: { + prototype: Credential; + new(): Credential; +}; + +interface CredentialsContainer { + create(options?: CredentialCreationOptions): Promise; + get(options?: CredentialRequestOptions): Promise; + preventSilentAccess(): Promise; + store(credential: Credential): Promise; +} + +declare var CredentialsContainer: { + prototype: CredentialsContainer; + new(): CredentialsContainer; +}; + /** Basic cryptography features available in the current context. It allows access to a cryptographically strong random number generator and to cryptographic primitives. */ interface Crypto { readonly subtle: SubtleCrypto; @@ -3534,8 +3668,7 @@ declare var CustomElementRegistry: { interface CustomEvent extends Event { /** - * Returns any custom data event was created with. - * Typically used for synthetic events. + * Returns any custom data event was created with. Typically used for synthetic events. */ readonly detail: T; initCustomEvent(typeArg: string, canBubbleArg: boolean, cancelableArg: boolean, detailArg: T): void; @@ -3859,8 +3992,7 @@ interface DOMStringList { */ readonly length: number; /** - * Returns true if strings contains string, and false - * otherwise. + * Returns true if strings contains string, and false otherwise. */ contains(string: string): boolean; /** @@ -3893,15 +4025,16 @@ interface DOMTokenList { readonly length: number; /** * Returns the associated set as string. + * * Can be set, to change the associated attribute. */ value: string; /** * Adds all arguments passed, except those already present. - * Throws a "SyntaxError" DOMException if one of the arguments is the empty - * string. - * Throws an "InvalidCharacterError" DOMException if one of the arguments - * contains any ASCII whitespace. + * + * Throws a "SyntaxError" DOMException if one of the arguments is the empty string. + * + * Throws an "InvalidCharacterError" DOMException if one of the arguments contains any ASCII whitespace. */ add(...tokens: string[]): void; /** @@ -3909,32 +4042,42 @@ interface DOMTokenList { */ contains(token: string): boolean; /** - * tokenlist[index] + * Returns the token with index index. */ item(index: number): string | null; /** * Removes arguments passed, if they are present. - * Throws a "SyntaxError" DOMException if one of the arguments is the empty - * string. - * Throws an "InvalidCharacterError" DOMException if one of the arguments - * contains any ASCII whitespace. + * + * Throws a "SyntaxError" DOMException if one of the arguments is the empty string. + * + * Throws an "InvalidCharacterError" DOMException if one of the arguments contains any ASCII whitespace. */ remove(...tokens: string[]): void; /** * Replaces token with newToken. + * * Returns true if token was replaced with newToken, and false otherwise. - * Throws a "SyntaxError" DOMException if one of the arguments is the empty - * string. - * Throws an "InvalidCharacterError" DOMException if one of the arguments - * contains any ASCII whitespace. + * + * Throws a "SyntaxError" DOMException if one of the arguments is the empty string. + * + * Throws an "InvalidCharacterError" DOMException if one of the arguments contains any ASCII whitespace. */ replace(oldToken: string, newToken: string): void; /** - * Returns true if token is in the associated attribute's supported tokens. Returns - * false otherwise. + * Returns true if token is in the associated attribute's supported tokens. Returns false otherwise. + * * Throws a TypeError if the associated attribute has no supported tokens defined. */ supports(token: string): boolean; + /** + * If force is not given, "toggles" token, removing it if it's present and adding it if it's not present. If force is true, adds token (same as add()). If force is false, removes token (same as remove()). + * + * Returns true if token is now present, and false otherwise. + * + * Throws a "SyntaxError" DOMException if token is empty. + * + * Throws an "InvalidCharacterError" DOMException if token contains any spaces. + */ toggle(token: string, force?: boolean): boolean; forEach(callbackfn: (value: string, key: number, parent: DOMTokenList) => void, thisArg?: any): void; [index: number]: string; @@ -3960,7 +4103,21 @@ declare var DataCue: { /** Used to hold the data that is being dragged during a drag and drop operation. It may hold one or more data items, each of one or more data types. For more information about drag and drop, see HTML Drag and Drop API. */ interface DataTransfer { + /** + * Returns the kind of operation that is currently selected. If the kind of operation isn't one of those that is allowed by the effectAllowed attribute, then the operation will fail. + * + * Can be set, to change the selected operation. + * + * The possible values are "none", "copy", "link", and "move". + */ dropEffect: string; + /** + * Returns the kinds of operations that are to be allowed. + * + * Can be set (during the dragstart event), to change the allowed operations. + * + * The possible values are "none", "copy", "copyLink", "copyMove", "link", "linkMove", "move", "all", and "uninitialized", + */ effectAllowed: string; /** * Returns a FileList of the files being dragged, if any. @@ -3971,8 +4128,7 @@ interface DataTransfer { */ readonly items: DataTransferItemList; /** - * Returns a frozen array listing the formats that were set in the dragstart event. In addition, if any files are being - * dragged, then one of the types will be the string "Files". + * Returns a frozen array listing the formats that were set in the dragstart event. In addition, if any files are being dragged, then one of the types will be the string "Files". */ readonly types: ReadonlyArray; /** @@ -3988,8 +4144,7 @@ interface DataTransfer { */ setData(format: string, data: string): void; /** - * Uses the given element to update the drag feedback, replacing any previously specified - * feedback. + * Uses the given element to update the drag feedback, replacing any previously specified feedback. */ setDragImage(image: Element, x: number, y: number): void; } @@ -4002,8 +4157,7 @@ declare var DataTransfer: { /** One drag data item. During a drag operation, each drag event has a dataTransfer property which contains a list of drag data items. Each item in the list is a DataTransferItem object. */ interface DataTransferItem { /** - * Returns the drag data item kind, one of: "string", - * "file". + * Returns the drag data item kind, one of: "string", "file". */ readonly kind: string; /** @@ -4015,8 +4169,7 @@ interface DataTransferItem { */ getAsFile(): File | null; /** - * Invokes the callback with the string data as the argument, if the drag data item - * kind is Plain Unicode string. + * Invokes the callback with the string data as the argument, if the drag data item kind is Plain Unicode string. */ getAsString(callback: FunctionStringCallback | null): void; webkitGetAsEntry(): any; @@ -4034,9 +4187,7 @@ interface DataTransferItemList { */ readonly length: number; /** - * Adds a new entry for the given data to the drag data store. If the data is plain - * text then a type string has to be provided - * also. + * Adds a new entry for the given data to the drag data store. If the data is plain text then a type string has to be provided also. */ add(data: string, type: string): DataTransferItem | null; add(data: File): DataTransferItem | null; @@ -4182,53 +4333,53 @@ interface DocumentEventMap extends GlobalEventHandlersEventMap, DocumentAndEleme /** Any web page loaded in the browser and serves as an entry point into the web page's content, which is the DOM tree. */ interface Document extends Node, NonElementParentNode, DocumentOrShadowRoot, ParentNode, XPathEvaluatorBase, GlobalEventHandlers, DocumentAndElementEventHandlers { - /** - * Sets or gets the URL for the current document. + /** + * Sets or gets the URL for the current document. */ readonly URL: string; - /** - * Gets the object that has the focus when the parent document has focus. + /** + * Gets the object that has the focus when the parent document has focus. */ readonly activeElement: Element | null; - /** - * Sets or gets the color of all active links in the document. + /** + * Sets or gets the color of all active links in the document. */ /** @deprecated */ alinkColor: string; - /** - * Returns a reference to the collection of elements contained by the object. + /** + * Returns a reference to the collection of elements contained by the object. */ /** @deprecated */ readonly all: HTMLAllCollection; - /** - * Retrieves a collection of all a objects that have a name and/or id property. Objects in this collection are in HTML source order. + /** + * Retrieves a collection of all a objects that have a name and/or id property. Objects in this collection are in HTML source order. */ /** @deprecated */ readonly anchors: HTMLCollectionOf; - /** - * Retrieves a collection of all applet objects in the document. + /** + * Retrieves a collection of all applet objects in the document. */ /** @deprecated */ readonly applets: HTMLCollectionOf; - /** - * Deprecated. Sets or retrieves a value that indicates the background color behind the object. + /** + * Deprecated. Sets or retrieves a value that indicates the background color behind the object. */ /** @deprecated */ bgColor: string; - /** - * Specifies the beginning and end of the document body. + /** + * Specifies the beginning and end of the document body. */ body: HTMLElement; /** * Returns document's encoding. */ readonly characterSet: string; - /** - * Gets or sets the character set used to encode the object. + /** + * Gets or sets the character set used to encode the object. */ readonly charset: string; - /** - * Gets a value that indicates whether standards-compliant mode is switched on for the object. + /** + * Gets a value that indicates whether standards-compliant mode is switched on for the object. */ readonly compatMode: string; /** @@ -4236,69 +4387,61 @@ interface Document extends Node, NonElementParentNode, DocumentOrShadowRoot, Par */ readonly contentType: string; /** - * Returns the HTTP cookies that apply to the Document. If there are no cookies or - * cookies can't be applied to this resource, the empty string will be returned. + * Returns the HTTP cookies that apply to the Document. If there are no cookies or cookies can't be applied to this resource, the empty string will be returned. + * * Can be set, to add a new cookie to the element's set of HTTP cookies. - * If the contents are sandboxed into a - * unique origin (e.g. in an iframe with the sandbox attribute), a - * "SecurityError" DOMException will be thrown on getting - * and setting. + * + * If the contents are sandboxed into a unique origin (e.g. in an iframe with the sandbox attribute), a "SecurityError" DOMException will be thrown on getting and setting. */ cookie: string; /** - * Returns the script element, or the SVG script element, - * that is currently executing, as long as the element represents a classic script. - * In the case of reentrant script execution, returns the one that most recently started executing - * amongst those that have not yet finished executing. - * Returns null if the Document is not currently executing a script - * or SVG script element (e.g., because the running script is an event - * handler, or a timeout), or if the currently executing script or SVG - * script element represents a module script. + * Returns the script element, or the SVG script element, that is currently executing, as long as the element represents a classic script. In the case of reentrant script execution, returns the one that most recently started executing amongst those that have not yet finished executing. + * + * Returns null if the Document is not currently executing a script or SVG script element (e.g., because the running script is an event handler, or a timeout), or if the currently executing script or SVG script element represents a module script. */ readonly currentScript: HTMLOrSVGScriptElement | null; readonly defaultView: WindowProxy | null; - /** - * Sets or gets a value that indicates whether the document can be edited. + /** + * Sets or gets a value that indicates whether the document can be edited. */ designMode: string; - /** - * Sets or retrieves a value that indicates the reading order of the object. + /** + * Sets or retrieves a value that indicates the reading order of the object. */ dir: string; - /** - * Gets an object representing the document type declaration associated with the current document. + /** + * Gets an object representing the document type declaration associated with the current document. */ readonly doctype: DocumentType | null; - /** - * Gets a reference to the root node of the document. + /** + * Gets a reference to the root node of the document. */ readonly documentElement: HTMLElement; /** * Returns document's URL. */ readonly documentURI: string; - /** - * Sets or gets the security domain of the document. + /** + * Sets or gets the security domain of the document. */ domain: string; - /** - * Retrieves a collection of all embed objects in the document. + /** + * Retrieves a collection of all embed objects in the document. */ readonly embeds: HTMLCollectionOf; - /** - * Sets or gets the foreground (text) color of the document. + /** + * Sets or gets the foreground (text) color of the document. */ /** @deprecated */ fgColor: string; - /** - * Retrieves a collection, in source order, of all form objects in the document. + /** + * Retrieves a collection, in source order, of all form objects in the document. */ readonly forms: HTMLCollectionOf; /** @deprecated */ readonly fullscreen: boolean; /** - * Returns true if document has the ability to display elements fullscreen - * and fullscreen is supported, or false otherwise. + * Returns true if document has the ability to display elements fullscreen and fullscreen is supported, or false otherwise. */ readonly fullscreenEnabled: boolean; /** @@ -4306,42 +4449,42 @@ interface Document extends Node, NonElementParentNode, DocumentOrShadowRoot, Par */ readonly head: HTMLHeadElement; readonly hidden: boolean; - /** - * Retrieves a collection, in source order, of img objects in the document. + /** + * Retrieves a collection, in source order, of img objects in the document. */ readonly images: HTMLCollectionOf; - /** - * Gets the implementation object of the current document. + /** + * Gets the implementation object of the current document. */ readonly implementation: DOMImplementation; - /** - * Returns the character encoding used to create the webpage that is loaded into the document object. + /** + * Returns the character encoding used to create the webpage that is loaded into the document object. */ readonly inputEncoding: string; - /** - * Gets the date that the page was last modified, if the page supplies one. + /** + * Gets the date that the page was last modified, if the page supplies one. */ readonly lastModified: string; - /** - * Sets or gets the color of the document links. + /** + * Sets or gets the color of the document links. */ /** @deprecated */ linkColor: string; - /** - * Retrieves a collection of all a objects that specify the href property and all area objects in the document. + /** + * Retrieves a collection of all a objects that specify the href property and all area objects in the document. */ readonly links: HTMLCollectionOf; - /** - * Contains information about the current URL. + /** + * Contains information about the current URL. */ location: Location; onfullscreenchange: ((this: Document, ev: Event) => any) | null; onfullscreenerror: ((this: Document, ev: Event) => any) | null; onpointerlockchange: ((this: Document, ev: Event) => any) | null; onpointerlockerror: ((this: Document, ev: Event) => any) | null; - /** - * Fires when the state of the object has changed. - * @param ev The event + /** + * Fires when the state of the object has changed. + * @param ev The event */ onreadystatechange: ((this: Document, ev: ProgressEvent) => any) | null; onvisibilitychange: ((this: Document, ev: Event) => any) | null; @@ -4353,34 +4496,34 @@ interface Document extends Node, NonElementParentNode, DocumentOrShadowRoot, Par * Return an HTMLCollection of the embed elements in the Document. */ readonly plugins: HTMLCollectionOf; - /** - * Retrieves a value that indicates the current state of the object. + /** + * Retrieves a value that indicates the current state of the object. */ readonly readyState: DocumentReadyState; - /** - * Gets the URL of the location that referred the user to the current page. + /** + * Gets the URL of the location that referred the user to the current page. */ readonly referrer: string; - /** - * Retrieves a collection of all script objects in the document. + /** + * Retrieves a collection of all script objects in the document. */ readonly scripts: HTMLCollectionOf; readonly scrollingElement: Element | null; readonly timeline: DocumentTimeline; - /** - * Contains the title of the document. + /** + * Contains the title of the document. */ title: string; readonly visibilityState: VisibilityState; - /** - * Sets or gets the color of the links that the user has visited. + /** + * Sets or gets the color of the links that the user has visited. */ /** @deprecated */ vlinkColor: string; /** * Moves node from another document and returns it. - * If node is a document, throws a "NotSupportedError" DOMException or, if node is a shadow root, throws a - * "HierarchyRequestError" DOMException. + * + * If node is a document, throws a "NotSupportedError" DOMException or, if node is a shadow root, throws a "HierarchyRequestError" DOMException. */ adoptNode(source: T): T; /** @deprecated */ @@ -4390,13 +4533,13 @@ interface Document extends Node, NonElementParentNode, DocumentOrShadowRoot, Par caretRangeFromPoint(x: number, y: number): Range; /** @deprecated */ clear(): void; - /** - * Closes an output stream and forces the sent data to display. + /** + * Closes an output stream and forces the sent data to display. */ close(): void; - /** - * Creates an attribute object with a specified name. - * @param name String that sets the attribute object's name. + /** + * Creates an attribute object with a specified name. + * @param name String that sets the attribute object's name. */ createAttribute(localName: string): Attr; createAttributeNS(namespace: string | null, qualifiedName: string): Attr; @@ -4404,35 +4547,36 @@ interface Document extends Node, NonElementParentNode, DocumentOrShadowRoot, Par * Returns a CDATASection node whose data is data. */ createCDATASection(data: string): CDATASection; - /** - * Creates a comment object with the specified data. - * @param data Sets the comment object's data. + /** + * Creates a comment object with the specified data. + * @param data Sets the comment object's data. */ createComment(data: string): Comment; - /** - * Creates a new document. + /** + * Creates a new document. */ createDocumentFragment(): DocumentFragment; - /** - * Creates an instance of the element for the specified tag. - * @param tagName The name of an element. + /** + * Creates an instance of the element for the specified tag. + * @param tagName The name of an element. */ createElement(tagName: K, options?: ElementCreationOptions): HTMLElementTagNameMap[K]; /** @deprecated */ createElement(tagName: K, options?: ElementCreationOptions): HTMLElementDeprecatedTagNameMap[K]; createElement(tagName: string, options?: ElementCreationOptions): HTMLElement; /** - * Returns an element with namespace namespace. Its namespace prefix will be everything before ":" (U+003E) in qualifiedName or null. Its local name will be everything after - * ":" (U+003E) in qualifiedName or qualifiedName. - * If localName does not match the Name production an - * "InvalidCharacterError" DOMException will be thrown. + * Returns an element with namespace namespace. Its namespace prefix will be everything before ":" (U+003E) in qualifiedName or null. Its local name will be everything after ":" (U+003E) in qualifiedName or qualifiedName. + * + * If localName does not match the Name production an "InvalidCharacterError" DOMException will be thrown. + * * If one of the following conditions is true a "NamespaceError" DOMException will be thrown: + * * localName does not match the QName production. * Namespace prefix is not null and namespace is the empty string. * Namespace prefix is "xml" and namespace is not the XML namespace. * qualifiedName or namespace prefix is "xmlns" and namespace is not the XMLNS namespace. - * namespace is the XMLNS namespace and - * neither qualifiedName nor namespace prefix is "xmlns". + * namespace is the XMLNS namespace and neither qualifiedName nor namespace prefix is "xmlns". + * * When supplied, options's is can be used to create a customized built-in element. */ createElementNS(namespaceURI: "http://www.w3.org/1999/xhtml", qualifiedName: string): HTMLElement; @@ -4455,11 +4599,13 @@ interface Document extends Node, NonElementParentNode, DocumentOrShadowRoot, Par createEvent(eventInterface: "ErrorEvent"): ErrorEvent; createEvent(eventInterface: "Event"): Event; createEvent(eventInterface: "Events"): Event; + createEvent(eventInterface: "FileReaderProgressEvent"): FileReaderProgressEvent; createEvent(eventInterface: "FocusEvent"): FocusEvent; createEvent(eventInterface: "FocusNavigationEvent"): FocusNavigationEvent; createEvent(eventInterface: "GamepadEvent"): GamepadEvent; createEvent(eventInterface: "HashChangeEvent"): HashChangeEvent; createEvent(eventInterface: "IDBVersionChangeEvent"): IDBVersionChangeEvent; + createEvent(eventInterface: "InputEvent"): InputEvent; createEvent(eventInterface: "KeyboardEvent"): KeyboardEvent; createEvent(eventInterface: "ListeningStateChangedEvent"): ListeningStateChangedEvent; createEvent(eventInterface: "MSGestureEvent"): MSGestureEvent; @@ -4518,152 +4664,147 @@ interface Document extends Node, NonElementParentNode, DocumentOrShadowRoot, Par createEvent(eventInterface: "WebGLContextEvent"): WebGLContextEvent; createEvent(eventInterface: "WheelEvent"): WheelEvent; createEvent(eventInterface: string): Event; - /** - * Creates a NodeIterator object that you can use to traverse filtered lists of nodes or elements in a document. - * @param root The root element or node to start traversing on. - * @param whatToShow The type of nodes or elements to appear in the node list - * @param filter A custom NodeFilter function to use. For more information, see filter. Use null for no filter. - * @param entityReferenceExpansion A flag that specifies whether entity reference nodes are expanded. + /** + * Creates a NodeIterator object that you can use to traverse filtered lists of nodes or elements in a document. + * @param root The root element or node to start traversing on. + * @param whatToShow The type of nodes or elements to appear in the node list + * @param filter A custom NodeFilter function to use. For more information, see filter. Use null for no filter. + * @param entityReferenceExpansion A flag that specifies whether entity reference nodes are expanded. */ createNodeIterator(root: Node, whatToShow?: number, filter?: NodeFilter | null): NodeIterator; /** - * Returns a ProcessingInstruction node whose target is target and data is data. - * If target does not match the Name production an - * "InvalidCharacterError" DOMException will be thrown. - * If data contains "?>" an - * "InvalidCharacterError" DOMException will be thrown. + * Returns a ProcessingInstruction node whose target is target and data is data. If target does not match the Name production an "InvalidCharacterError" DOMException will be thrown. If data contains "?>" an "InvalidCharacterError" DOMException will be thrown. */ createProcessingInstruction(target: string, data: string): ProcessingInstruction; - /** - * Returns an empty range object that has both of its boundary points positioned at the beginning of the document. + /** + * Returns an empty range object that has both of its boundary points positioned at the beginning of the document. */ createRange(): Range; - /** - * Creates a text string from the specified value. - * @param data String that specifies the nodeValue property of the text node. + /** + * Creates a text string from the specified value. + * @param data String that specifies the nodeValue property of the text node. */ createTextNode(data: string): Text; - createTouch(view: WindowProxy, target: EventTarget, identifier: number, pageX: number, pageY: number, screenX: number, screenY: number): Touch; - createTouchList(...touches: Touch[]): TouchList; - /** - * Creates a TreeWalker object that you can use to traverse filtered lists of nodes or elements in a document. - * @param root The root element or node to start traversing on. - * @param whatToShow The type of nodes or elements to appear in the node list. For more information, see whatToShow. - * @param filter A custom NodeFilter function to use. - * @param entityReferenceExpansion A flag that specifies whether entity reference nodes are expanded. + /** + * Creates a TreeWalker object that you can use to traverse filtered lists of nodes or elements in a document. + * @param root The root element or node to start traversing on. + * @param whatToShow The type of nodes or elements to appear in the node list. For more information, see whatToShow. + * @param filter A custom NodeFilter function to use. + * @param entityReferenceExpansion A flag that specifies whether entity reference nodes are expanded. */ createTreeWalker(root: Node, whatToShow?: number, filter?: NodeFilter | null): TreeWalker; /** @deprecated */ createTreeWalker(root: Node, whatToShow: number, filter: NodeFilter | null, entityReferenceExpansion?: boolean): TreeWalker; - /** - * Returns the element for the specified x coordinate and the specified y coordinate. - * @param x The x-offset - * @param y The y-offset + /** + * Returns the element for the specified x coordinate and the specified y coordinate. + * @param x The x-offset + * @param y The y-offset */ elementFromPoint(x: number, y: number): Element | null; elementsFromPoint(x: number, y: number): Element[]; - /** - * Executes a command on the current document, current selection, or the given range. - * @param commandId String that specifies the command to execute. This command can be any of the command identifiers that can be executed in script. - * @param showUI Display the user interface, defaults to false. - * @param value Value to assign. + /** + * Executes a command on the current document, current selection, or the given range. + * @param commandId String that specifies the command to execute. This command can be any of the command identifiers that can be executed in script. + * @param showUI Display the user interface, defaults to false. + * @param value Value to assign. */ execCommand(commandId: string, showUI?: boolean, value?: string): boolean; /** - * Stops document's fullscreen element from being displayed fullscreen and - * resolves promise when done. + * Stops document's fullscreen element from being displayed fullscreen and resolves promise when done. */ exitFullscreen(): Promise; exitPointerLock(): void; getAnimations(): Animation[]; - /** - * Returns a reference to the first object with the specified value of the ID or NAME attribute. - * @param elementId String that specifies the ID value. Case-insensitive. + /** + * Returns a reference to the first object with the specified value of the ID or NAME attribute. + * @param elementId String that specifies the ID value. Case-insensitive. */ getElementById(elementId: string): HTMLElement | null; /** - * collection = element . getElementsByClassName(classNames) + * Returns a HTMLCollection of the elements in the object on which the method was invoked (a document or an element) that have all the classes given by classNames. The classNames argument is interpreted as a space-separated list of classes. */ getElementsByClassName(classNames: string): HTMLCollectionOf; - /** - * Gets a collection of objects based on the value of the NAME or ID attribute. - * @param elementName Gets a collection of objects based on the value of the NAME or ID attribute. + /** + * Gets a collection of objects based on the value of the NAME or ID attribute. + * @param elementName Gets a collection of objects based on the value of the NAME or ID attribute. */ getElementsByName(elementName: string): NodeListOf; - /** - * Retrieves a collection of objects based on the specified element name. - * @param name Specifies the name of an element. + /** + * Retrieves a collection of objects based on the specified element name. + * @param name Specifies the name of an element. */ getElementsByTagName(qualifiedName: K): HTMLCollectionOf; getElementsByTagName(qualifiedName: K): HTMLCollectionOf; getElementsByTagName(qualifiedName: string): HTMLCollectionOf; /** - * If namespace and localName are - * "*" returns a HTMLCollection of all descendant elements. + * If namespace and localName are "*" returns a HTMLCollection of all descendant elements. + * * If only namespace is "*" returns a HTMLCollection of all descendant elements whose local name is localName. + * * If only localName is "*" returns a HTMLCollection of all descendant elements whose namespace is namespace. + * * Otherwise, returns a HTMLCollection of all descendant elements whose namespace is namespace and local name is localName. */ getElementsByTagNameNS(namespaceURI: "http://www.w3.org/1999/xhtml", localName: string): HTMLCollectionOf; getElementsByTagNameNS(namespaceURI: "http://www.w3.org/2000/svg", localName: string): HTMLCollectionOf; getElementsByTagNameNS(namespaceURI: string, localName: string): HTMLCollectionOf; - /** - * Returns an object representing the current selection of the document that is loaded into the object displaying a webpage. + /** + * Returns an object representing the current selection of the document that is loaded into the object displaying a webpage. */ getSelection(): Selection | null; - /** - * Gets a value indicating whether the object currently has focus. + /** + * Gets a value indicating whether the object currently has focus. */ hasFocus(): boolean; /** * Returns a copy of node. If deep is true, the copy also includes the node's descendants. - * If node is a document or a shadow root, throws a - * "NotSupportedError" DOMException. + * + * If node is a document or a shadow root, throws a "NotSupportedError" DOMException. */ importNode(importedNode: T, deep: boolean): T; - /** - * Opens a new window and loads a document specified by a given URL. Also, opens a new window that uses the url parameter and the name parameter to collect the output of the write method and the writeln method. - * @param url Specifies a MIME type for the document. - * @param name Specifies the name of the window. This name is used as the value for the TARGET attribute on a form or an anchor element. - * @param features Contains a list of items separated by commas. Each item consists of an option and a value, separated by an equals sign (for example, "fullscreen=yes, toolbar=yes"). The following values are supported. - * @param replace Specifies whether the existing entry for the document is replaced in the history list. + /** + * Opens a new window and loads a document specified by a given URL. Also, opens a new window that uses the url parameter and the name parameter to collect the output of the write method and the writeln method. + * @param url Specifies a MIME type for the document. + * @param name Specifies the name of the window. This name is used as the value for the TARGET attribute on a form or an anchor element. + * @param features Contains a list of items separated by commas. Each item consists of an option and a value, separated by an equals sign (for example, "fullscreen=yes, toolbar=yes"). The following values are supported. + * @param replace Specifies whether the existing entry for the document is replaced in the history list. */ open(url?: string, name?: string, features?: string, replace?: boolean): Document; - /** - * Returns a Boolean value that indicates whether a specified command can be successfully executed using execCommand, given the current state of the document. - * @param commandId Specifies a command identifier. + /** + * Returns a Boolean value that indicates whether a specified command can be successfully executed using execCommand, given the current state of the document. + * @param commandId Specifies a command identifier. */ queryCommandEnabled(commandId: string): boolean; - /** - * Returns a Boolean value that indicates whether the specified command is in the indeterminate state. - * @param commandId String that specifies a command identifier. + /** + * Returns a Boolean value that indicates whether the specified command is in the indeterminate state. + * @param commandId String that specifies a command identifier. */ queryCommandIndeterm(commandId: string): boolean; - /** - * Returns a Boolean value that indicates the current state of the command. - * @param commandId String that specifies a command identifier. + /** + * Returns a Boolean value that indicates the current state of the command. + * @param commandId String that specifies a command identifier. */ queryCommandState(commandId: string): boolean; - /** - * Returns a Boolean value that indicates whether the current command is supported on the current range. - * @param commandId Specifies a command identifier. + /** + * Returns a Boolean value that indicates whether the current command is supported on the current range. + * @param commandId Specifies a command identifier. */ queryCommandSupported(commandId: string): boolean; - /** - * Returns the current value of the document, range, or current selection for the given command. - * @param commandId String that specifies a command identifier. + /** + * Returns the current value of the document, range, or current selection for the given command. + * @param commandId String that specifies a command identifier. */ queryCommandValue(commandId: string): string; /** @deprecated */ releaseEvents(): void; - /** - * Writes one or more HTML expressions to a document in the specified window. - * @param content Specifies the text and HTML tags to write. + /** + * Writes one or more HTML expressions to a document in the specified window. + * @param content Specifies the text and HTML tags to write. */ write(...text: string[]): void; - /** - * Writes one or more HTML expressions, followed by a carriage return, to a document in the specified window. - * @param content The text and HTML tags to write. + /** + * Writes one or more HTML expressions, followed by a carriage return, to a document in the specified window. + * @param content The text and HTML tags to write. */ writeln(...text: string[]): void; addEventListener(type: K, listener: (this: Document, ev: DocumentEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void; @@ -4709,11 +4850,13 @@ interface DocumentEvent { createEvent(eventInterface: "ErrorEvent"): ErrorEvent; createEvent(eventInterface: "Event"): Event; createEvent(eventInterface: "Events"): Event; + createEvent(eventInterface: "FileReaderProgressEvent"): FileReaderProgressEvent; createEvent(eventInterface: "FocusEvent"): FocusEvent; createEvent(eventInterface: "FocusNavigationEvent"): FocusNavigationEvent; createEvent(eventInterface: "GamepadEvent"): GamepadEvent; createEvent(eventInterface: "HashChangeEvent"): HashChangeEvent; createEvent(eventInterface: "IDBVersionChangeEvent"): IDBVersionChangeEvent; + createEvent(eventInterface: "InputEvent"): InputEvent; createEvent(eventInterface: "KeyboardEvent"): KeyboardEvent; createEvent(eventInterface: "ListeningStateChangedEvent"): ListeningStateChangedEvent; createEvent(eventInterface: "MSGestureEvent"): MSGestureEvent; @@ -4786,10 +4929,13 @@ declare var DocumentFragment: { interface DocumentOrShadowRoot { readonly activeElement: Element | null; + /** + * Returns document's fullscreen element. + */ readonly fullscreenElement: Element | null; readonly pointerLockElement: Element | null; - /** - * Retrieves a collection of styleSheet objects representing the style sheets that correspond to each instance of a link or style object in the document. + /** + * Retrieves a collection of styleSheet objects representing the style sheets that correspond to each instance of a link or style object in the document. */ readonly styleSheets: StyleSheetList; caretPositionFromPoint(x: number, y: number): CaretPosition | null; @@ -4883,13 +5029,11 @@ interface Element extends Node, ParentNode, NonDocumentTypeChildNode, ChildNode, readonly assignedSlot: HTMLSlotElement | null; readonly attributes: NamedNodeMap; /** - * Allows for manipulation of element's class content attribute as a - * set of whitespace-separated tokens through a DOMTokenList object. + * Allows for manipulation of element's class content attribute as a set of whitespace-separated tokens through a DOMTokenList object. */ readonly classList: DOMTokenList; /** - * Returns the value of element's class content attribute. Can be set - * to change it. + * Returns the value of element's class content attribute. Can be set to change it. */ className: string; readonly clientHeight: number; @@ -4897,8 +5041,7 @@ interface Element extends Node, ParentNode, NonDocumentTypeChildNode, ChildNode, readonly clientTop: number; readonly clientWidth: number; /** - * Returns the value of element's id content attribute. Can be set to - * change it. + * Returns the value of element's id content attribute. Can be set to change it. */ id: string; /** @@ -4925,8 +5068,7 @@ interface Element extends Node, ParentNode, NonDocumentTypeChildNode, ChildNode, */ readonly shadowRoot: ShadowRoot | null; /** - * Returns the value of element's slot content attribute. Can be set to - * change it. + * Returns the value of element's slot content attribute. Can be set to change it. */ slot: string; /** @@ -4942,25 +5084,26 @@ interface Element extends Node, ParentNode, NonDocumentTypeChildNode, ChildNode, */ closest(selector: K): HTMLElementTagNameMap[K] | null; closest(selector: K): SVGElementTagNameMap[K] | null; - closest(selector: string): Element | null; + closest(selector: string): E | null; /** * Returns element's first attribute whose qualified name is qualifiedName, and null if there is no such attribute otherwise. */ getAttribute(qualifiedName: string): string | null; /** - * Returns element's attribute whose namespace is namespace and local name is localName, and null if there is - * no such attribute otherwise. + * Returns element's attribute whose namespace is namespace and local name is localName, and null if there is no such attribute otherwise. */ getAttributeNS(namespace: string | null, localName: string): string | null; /** - * Returns the qualified names of all element's attributes. - * Can contain duplicates. + * Returns the qualified names of all element's attributes. Can contain duplicates. */ getAttributeNames(): string[]; getAttributeNode(name: string): Attr | null; getAttributeNodeNS(namespaceURI: string, localName: string): Attr | null; getBoundingClientRect(): ClientRect | DOMRect; getClientRects(): ClientRectList | DOMRectList; + /** + * Returns a HTMLCollection of the elements in the object on which the method was invoked (a document or an element) that have all the classes given by classNames. The classNames argument is interpreted as a space-separated list of classes. + */ getElementsByClassName(classNames: string): HTMLCollectionOf; getElementsByTagName(qualifiedName: K): HTMLCollectionOf; getElementsByTagName(qualifiedName: K): HTMLCollectionOf; @@ -5001,11 +5144,8 @@ interface Element extends Node, ParentNode, NonDocumentTypeChildNode, ChildNode, removeAttributeNode(attr: Attr): Attr; /** * Displays element fullscreen and resolves promise when done. - * When supplied, options's navigationUI member indicates whether showing - * navigation UI while in fullscreen is preferred or not. If set to "show", navigation - * simplicity is preferred over screen space, and if set to "hide", more screen space - * is preferred. User agents are always free to honor user preference over the application's. The - * default value "auto" indicates no application preference. + * + * When supplied, options's navigationUI member indicates whether showing navigation UI while in fullscreen is preferred or not. If set to "show", navigation simplicity is preferred over screen space, and if set to "hide", more screen space is preferred. User agents are always free to honor user preference over the application's. The default value "auto" indicates no application preference. */ requestFullscreen(options?: FullscreenOptions): Promise; requestPointerLock(): void; @@ -5028,8 +5168,8 @@ interface Element extends Node, ParentNode, NonDocumentTypeChildNode, ChildNode, setAttributeNodeNS(attr: Attr): Attr | null; setPointerCapture(pointerId: number): void; /** - * If force is not given, "toggles" qualifiedName, removing it if it is - * present and adding it if it is not present. If force is true, adds qualifiedName. If force is false, removes qualifiedName. + * If force is not given, "toggles" qualifiedName, removing it if it is present and adding it if it is not present. If force is true, adds qualifiedName. If force is false, removes qualifiedName. + * * Returns true if qualifiedName is now present, and false otherwise. */ toggleAttribute(qualifiedName: string, force?: boolean): boolean; @@ -5076,21 +5216,28 @@ interface Event { */ readonly bubbles: boolean; cancelBubble: boolean; + /** + * Returns true or false depending on how event was initialized. Its return value does not always carry meaning, but true can indicate that part of the operation during which event was dispatched, can be canceled by invoking the preventDefault() method. + */ readonly cancelable: boolean; /** * Returns true or false depending on how event was initialized. True if event invokes listeners past a ShadowRoot node that is the root of its target, and false otherwise. */ readonly composed: boolean; /** - * Returns the object whose event listener's callback is currently being - * invoked. + * Returns the object whose event listener's callback is currently being invoked. */ readonly currentTarget: EventTarget | null; + /** + * Returns true if preventDefault() was invoked successfully to indicate cancelation, and false otherwise. + */ readonly defaultPrevented: boolean; + /** + * Returns the event's phase, which is one of NONE, CAPTURING_PHASE, AT_TARGET, and BUBBLING_PHASE. + */ readonly eventPhase: number; /** - * Returns true if event was dispatched by the user agent, and - * false otherwise. + * Returns true if event was dispatched by the user agent, and false otherwise. */ readonly isTrusted: boolean; returnValue: boolean; @@ -5101,23 +5248,24 @@ interface Event { */ readonly target: EventTarget | null; /** - * Returns the event's timestamp as the number of milliseconds measured relative to - * the time origin. + * Returns the event's timestamp as the number of milliseconds measured relative to the time origin. */ readonly timeStamp: number; /** - * Returns the type of event, e.g. - * "click", "hashchange", or - * "submit". + * Returns the type of event, e.g. "click", "hashchange", or "submit". */ readonly type: string; + /** + * Returns the item objects of event's path (objects on which listeners will be invoked), except for any nodes in shadow trees of which the shadow root's mode is "closed" that are not reachable from event's currentTarget. + */ composedPath(): EventTarget[]; initEvent(type: string, bubbles?: boolean, cancelable?: boolean): void; + /** + * If invoked when the cancelable attribute value is true, and while executing a listener for the event with passive set to false, signals to the operation that caused event to be dispatched that it needs to be canceled. + */ preventDefault(): void; /** - * Invoking this method prevents event from reaching - * any registered event listeners after the current one finishes running and, when dispatched in a tree, also prevents event from reaching any - * other objects. + * Invoking this method prevents event from reaching any registered event listeners after the current one finishes running and, when dispatched in a tree, also prevents event from reaching any other objects. */ stopImmediatePropagation(): void; /** @@ -5154,8 +5302,7 @@ interface EventSource extends EventTarget { onmessage: ((this: EventSource, ev: MessageEvent) => any) | null; onopen: ((this: EventSource, ev: Event) => any) | null; /** - * Returns the state of this EventSource object's connection. It can have the - * values described below. + * Returns the state of this EventSource object's connection. It can have the values described below. */ readonly readyState: number; /** @@ -5163,11 +5310,12 @@ interface EventSource extends EventTarget { */ readonly url: string; /** - * Returns true if the credentials mode - * for connection requests to the URL providing the - * event stream is set to "include", and false otherwise. + * Returns true if the credentials mode for connection requests to the URL providing the event stream is set to "include", and false otherwise. */ readonly withCredentials: boolean; + /** + * Aborts any instances of the fetch algorithm started for this EventSource object, and sets the readyState attribute to CLOSED. + */ close(): void; readonly CLOSED: number; readonly CONNECTING: number; @@ -5190,18 +5338,20 @@ declare var EventSource: { interface EventTarget { /** * Appends an event listener for events whose type attribute value is type. The callback argument sets the callback that will be invoked when the event is dispatched. - * The options argument sets listener-specific options. For compatibility this can be a - * boolean, in which case the method behaves exactly as if the value was specified as options's capture. + * + * The options argument sets listener-specific options. For compatibility this can be a boolean, in which case the method behaves exactly as if the value was specified as options's capture. + * * When set to true, options's capture prevents callback from being invoked when the event's eventPhase attribute value is BUBBLING_PHASE. When false (or not present), callback will not be invoked when event's eventPhase attribute value is CAPTURING_PHASE. Either way, callback will be invoked if event's eventPhase attribute value is AT_TARGET. + * * When set to true, options's passive indicates that the callback will not cancel the event by invoking preventDefault(). This is used to enable performance optimizations described in §2.8 Observing event listeners. - * When set to true, options's once indicates that the callback will only be invoked once after which the event listener will - * be removed. + * + * When set to true, options's once indicates that the callback will only be invoked once after which the event listener will be removed. + * * The event listener is appended to target's event listener list and is not appended if it has the same type, callback, and capture. */ addEventListener(type: string, listener: EventListenerOrEventListenerObject | null, options?: boolean | AddEventListenerOptions): void; /** - * Dispatches a synthetic event event to target and returns true - * if either event's cancelable attribute value is false or its preventDefault() method was not invoked, and false otherwise. + * Dispatches a synthetic event event to target and returns true if either event's cancelable attribute value is false or its preventDefault() method was not invoked, and false otherwise. */ dispatchEvent(event: Event): boolean; /** @@ -5239,6 +5389,11 @@ interface External { IsSearchProviderInstalled(): void; } +declare var External: { + prototype: External; + new(): External; +}; + /** Provides information about files and allows JavaScript in a web page to access their content. */ interface File extends Blob { readonly lastModified: number; @@ -5263,23 +5418,23 @@ declare var FileList: { }; interface FileReaderEventMap { - "abort": ProgressEvent; - "error": ProgressEvent; - "load": ProgressEvent; - "loadend": ProgressEvent; - "loadstart": ProgressEvent; - "progress": ProgressEvent; + "abort": FileReaderProgressEvent; + "error": FileReaderProgressEvent; + "load": FileReaderProgressEvent; + "loadend": FileReaderProgressEvent; + "loadstart": FileReaderProgressEvent; + "progress": FileReaderProgressEvent; } /** Lets web applications asynchronously read the contents of files (or raw data buffers) stored on the user's computer, using File or Blob objects to specify the file or data to read. */ interface FileReader extends EventTarget { readonly error: DOMException | null; - onabort: ((this: FileReader, ev: ProgressEvent) => any) | null; - onerror: ((this: FileReader, ev: ProgressEvent) => any) | null; - onload: ((this: FileReader, ev: ProgressEvent) => any) | null; - onloadend: ((this: FileReader, ev: ProgressEvent) => any) | null; - onloadstart: ((this: FileReader, ev: ProgressEvent) => any) | null; - onprogress: ((this: FileReader, ev: ProgressEvent) => any) | null; + onabort: ((this: FileReader, ev: FileReaderProgressEvent) => any) | null; + onerror: ((this: FileReader, ev: FileReaderProgressEvent) => any) | null; + onload: ((this: FileReader, ev: FileReaderProgressEvent) => any) | null; + onloadend: ((this: FileReader, ev: FileReaderProgressEvent) => any) | null; + onloadstart: ((this: FileReader, ev: FileReaderProgressEvent) => any) | null; + onprogress: ((this: FileReader, ev: FileReaderProgressEvent) => any) | null; readonly readyState: number; readonly result: string | ArrayBuffer | null; abort(): void; @@ -5304,15 +5459,18 @@ declare var FileReader: { readonly LOADING: number; }; +interface FileReaderProgressEvent extends ProgressEvent { + readonly target: FileReader | null; +} + /** Focus-related events like focus, blur, focusin, or focusout. */ interface FocusEvent extends UIEvent { - readonly relatedTarget: EventTarget; - initFocusEvent(typeArg: string, canBubbleArg: boolean, cancelableArg: boolean, viewArg: Window, detailArg: number, relatedTargetArg: EventTarget): void; + readonly relatedTarget: EventTarget | null; } declare var FocusEvent: { prototype: FocusEvent; - new(typeArg: string, eventInitDict?: FocusEventInit): FocusEvent; + new(type: string, eventInitDict?: FocusEventInit): FocusEvent; }; interface FocusNavigationEvent extends Event { @@ -5355,7 +5513,7 @@ declare var GainNode: { new(context: BaseAudioContext, options?: GainOptions): GainNode; }; -/** An interface of the Gamepad API defines an individual gamepad or other controller, allowing access to information such as button presses, axis positions, and id. */ +/** This Gamepad API interface defines an individual gamepad or other controller, allowing access to information such as button presses, axis positions, and id. */ interface Gamepad { readonly axes: ReadonlyArray; readonly buttons: ReadonlyArray; @@ -5386,7 +5544,7 @@ declare var GamepadButton: { new(): GamepadButton; }; -/** An interface of the Gamepad API contains references to gamepads connected to the system, which is what the gamepad events Window.gamepadconnected and Window.gamepaddisconnected are fired in response to. */ +/** This Gamepad API interface contains references to gamepads connected to the system, which is what the gamepad events Window.gamepadconnected and Window.gamepaddisconnected are fired in response to. */ interface GamepadEvent extends Event { readonly gamepad: Gamepad; } @@ -5396,7 +5554,7 @@ declare var GamepadEvent: { new(type: string, eventInitDict: GamepadEventInit): GamepadEvent; }; -/** An interface of the Gamepad API represents hardware in the controller designed to provide haptic feedback to the user (if available), most commonly vibration hardware. */ +/** This Gamepad API interface represents hardware in the controller designed to provide haptic feedback to the user (if available), most commonly vibration hardware. */ interface GamepadHapticActuator { readonly type: GamepadHapticActuatorType; pulse(value: number, duration: number): Promise; @@ -5407,7 +5565,7 @@ declare var GamepadHapticActuator: { new(): GamepadHapticActuator; }; -/** An interface of the Gamepad API represents the pose of a WebVR controller at a given timestamp (which includes orientation, position, velocity, and acceleration information.) */ +/** This Gamepad API interface represents the pose of a WebVR controller at a given timestamp (which includes orientation, position, velocity, and acceleration information.) */ interface GamepadPose { readonly angularAcceleration: Float32Array | null; readonly angularVelocity: Float32Array | null; @@ -5425,13 +5583,23 @@ declare var GamepadPose: { }; interface GenericTransformStream { + /** + * Returns a readable stream whose chunks are strings resulting from running encoding's decoder on the chunks written to writable. + */ readonly readable: ReadableStream; /** - * Returns a writable stream which accepts string chunks and runs them through UTF-8's encoder before making them available to readable. + * Returns a writable stream which accepts BufferSource chunks and runs them through encoding's decoder before making them available to readable. + * * Typically this will be used via the pipeThrough() method on a ReadableStream source. - * textReadable - * .pipeThrough(new TextEncoderStream()) - * .pipeTo(byteWritable); + * + * ``` + * var decoder = new TextDecoderStream(encoding); + * byteReadable + * .pipeThrough(decoder) + * .pipeTo(textWritable); + * ``` + * + * If the error mode is "fatal" and encoding's decoder returns error, both readable and writable will be errored with a TypeError. */ readonly writable: WritableStream; } @@ -5473,6 +5641,8 @@ interface GlobalEventHandlersEventMap { "ended": Event; "error": ErrorEvent; "focus": FocusEvent; + "focusin": FocusEvent; + "focusout": FocusEvent; "gotpointercapture": PointerEvent; "input": Event; "invalid": Event; @@ -5533,9 +5703,9 @@ interface GlobalEventHandlersEventMap { } interface GlobalEventHandlers { - /** - * Fires when the user aborts the download. - * @param ev The event. + /** + * Fires when the user aborts the download. + * @param ev The event. */ onabort: ((this: GlobalEventHandlers, ev: UIEvent) => any) | null; onanimationcancel: ((this: GlobalEventHandlers, ev: AnimationEvent) => any) | null; @@ -5543,177 +5713,177 @@ interface GlobalEventHandlers { onanimationiteration: ((this: GlobalEventHandlers, ev: AnimationEvent) => any) | null; onanimationstart: ((this: GlobalEventHandlers, ev: AnimationEvent) => any) | null; onauxclick: ((this: GlobalEventHandlers, ev: MouseEvent) => any) | null; - /** - * Fires when the object loses the input focus. - * @param ev The focus event. + /** + * Fires when the object loses the input focus. + * @param ev The focus event. */ onblur: ((this: GlobalEventHandlers, ev: FocusEvent) => any) | null; oncancel: ((this: GlobalEventHandlers, ev: Event) => any) | null; - /** - * Occurs when playback is possible, but would require further buffering. - * @param ev The event. + /** + * Occurs when playback is possible, but would require further buffering. + * @param ev The event. */ oncanplay: ((this: GlobalEventHandlers, ev: Event) => any) | null; oncanplaythrough: ((this: GlobalEventHandlers, ev: Event) => any) | null; - /** - * Fires when the contents of the object or selection have changed. - * @param ev The event. + /** + * Fires when the contents of the object or selection have changed. + * @param ev The event. */ onchange: ((this: GlobalEventHandlers, ev: Event) => any) | null; - /** - * Fires when the user clicks the left mouse button on the object - * @param ev The mouse event. + /** + * Fires when the user clicks the left mouse button on the object + * @param ev The mouse event. */ onclick: ((this: GlobalEventHandlers, ev: MouseEvent) => any) | null; onclose: ((this: GlobalEventHandlers, ev: Event) => any) | null; - /** - * Fires when the user clicks the right mouse button in the client area, opening the context menu. - * @param ev The mouse event. + /** + * Fires when the user clicks the right mouse button in the client area, opening the context menu. + * @param ev The mouse event. */ oncontextmenu: ((this: GlobalEventHandlers, ev: MouseEvent) => any) | null; oncuechange: ((this: GlobalEventHandlers, ev: Event) => any) | null; - /** - * Fires when the user double-clicks the object. - * @param ev The mouse event. + /** + * Fires when the user double-clicks the object. + * @param ev The mouse event. */ ondblclick: ((this: GlobalEventHandlers, ev: MouseEvent) => any) | null; - /** - * Fires on the source object continuously during a drag operation. - * @param ev The event. + /** + * Fires on the source object continuously during a drag operation. + * @param ev The event. */ ondrag: ((this: GlobalEventHandlers, ev: DragEvent) => any) | null; - /** - * Fires on the source object when the user releases the mouse at the close of a drag operation. - * @param ev The event. + /** + * Fires on the source object when the user releases the mouse at the close of a drag operation. + * @param ev The event. */ ondragend: ((this: GlobalEventHandlers, ev: DragEvent) => any) | null; - /** - * Fires on the target element when the user drags the object to a valid drop target. - * @param ev The drag event. + /** + * Fires on the target element when the user drags the object to a valid drop target. + * @param ev The drag event. */ ondragenter: ((this: GlobalEventHandlers, ev: DragEvent) => any) | null; ondragexit: ((this: GlobalEventHandlers, ev: Event) => any) | null; - /** - * Fires on the target object when the user moves the mouse out of a valid drop target during a drag operation. - * @param ev The drag event. + /** + * Fires on the target object when the user moves the mouse out of a valid drop target during a drag operation. + * @param ev The drag event. */ ondragleave: ((this: GlobalEventHandlers, ev: DragEvent) => any) | null; - /** - * Fires on the target element continuously while the user drags the object over a valid drop target. - * @param ev The event. + /** + * Fires on the target element continuously while the user drags the object over a valid drop target. + * @param ev The event. */ ondragover: ((this: GlobalEventHandlers, ev: DragEvent) => any) | null; - /** - * Fires on the source object when the user starts to drag a text selection or selected object. - * @param ev The event. + /** + * Fires on the source object when the user starts to drag a text selection or selected object. + * @param ev The event. */ ondragstart: ((this: GlobalEventHandlers, ev: DragEvent) => any) | null; ondrop: ((this: GlobalEventHandlers, ev: DragEvent) => any) | null; - /** - * Occurs when the duration attribute is updated. - * @param ev The event. + /** + * Occurs when the duration attribute is updated. + * @param ev The event. */ ondurationchange: ((this: GlobalEventHandlers, ev: Event) => any) | null; - /** - * Occurs when the media element is reset to its initial state. - * @param ev The event. + /** + * Occurs when the media element is reset to its initial state. + * @param ev The event. */ onemptied: ((this: GlobalEventHandlers, ev: Event) => any) | null; - /** - * Occurs when the end of playback is reached. - * @param ev The event + /** + * Occurs when the end of playback is reached. + * @param ev The event */ onended: ((this: GlobalEventHandlers, ev: Event) => any) | null; - /** - * Fires when an error occurs during object loading. - * @param ev The event. + /** + * Fires when an error occurs during object loading. + * @param ev The event. */ onerror: OnErrorEventHandler; - /** - * Fires when the object receives focus. - * @param ev The event. + /** + * Fires when the object receives focus. + * @param ev The event. */ onfocus: ((this: GlobalEventHandlers, ev: FocusEvent) => any) | null; ongotpointercapture: ((this: GlobalEventHandlers, ev: PointerEvent) => any) | null; oninput: ((this: GlobalEventHandlers, ev: Event) => any) | null; oninvalid: ((this: GlobalEventHandlers, ev: Event) => any) | null; - /** - * Fires when the user presses a key. - * @param ev The keyboard event + /** + * Fires when the user presses a key. + * @param ev The keyboard event */ onkeydown: ((this: GlobalEventHandlers, ev: KeyboardEvent) => any) | null; - /** - * Fires when the user presses an alphanumeric key. - * @param ev The event. + /** + * Fires when the user presses an alphanumeric key. + * @param ev The event. */ onkeypress: ((this: GlobalEventHandlers, ev: KeyboardEvent) => any) | null; - /** - * Fires when the user releases a key. - * @param ev The keyboard event + /** + * Fires when the user releases a key. + * @param ev The keyboard event */ onkeyup: ((this: GlobalEventHandlers, ev: KeyboardEvent) => any) | null; - /** - * Fires immediately after the browser loads the object. - * @param ev The event. + /** + * Fires immediately after the browser loads the object. + * @param ev The event. */ onload: ((this: GlobalEventHandlers, ev: Event) => any) | null; - /** - * Occurs when media data is loaded at the current playback position. - * @param ev The event. + /** + * Occurs when media data is loaded at the current playback position. + * @param ev The event. */ onloadeddata: ((this: GlobalEventHandlers, ev: Event) => any) | null; - /** - * Occurs when the duration and dimensions of the media have been determined. - * @param ev The event. + /** + * Occurs when the duration and dimensions of the media have been determined. + * @param ev The event. */ onloadedmetadata: ((this: GlobalEventHandlers, ev: Event) => any) | null; onloadend: ((this: GlobalEventHandlers, ev: ProgressEvent) => any) | null; - /** - * Occurs when Internet Explorer begins looking for media data. - * @param ev The event. + /** + * Occurs when Internet Explorer begins looking for media data. + * @param ev The event. */ onloadstart: ((this: GlobalEventHandlers, ev: Event) => any) | null; onlostpointercapture: ((this: GlobalEventHandlers, ev: PointerEvent) => any) | null; - /** - * Fires when the user clicks the object with either mouse button. - * @param ev The mouse event. + /** + * Fires when the user clicks the object with either mouse button. + * @param ev The mouse event. */ onmousedown: ((this: GlobalEventHandlers, ev: MouseEvent) => any) | null; onmouseenter: ((this: GlobalEventHandlers, ev: MouseEvent) => any) | null; onmouseleave: ((this: GlobalEventHandlers, ev: MouseEvent) => any) | null; - /** - * Fires when the user moves the mouse over the object. - * @param ev The mouse event. + /** + * Fires when the user moves the mouse over the object. + * @param ev The mouse event. */ onmousemove: ((this: GlobalEventHandlers, ev: MouseEvent) => any) | null; - /** - * Fires when the user moves the mouse pointer outside the boundaries of the object. - * @param ev The mouse event. + /** + * Fires when the user moves the mouse pointer outside the boundaries of the object. + * @param ev The mouse event. */ onmouseout: ((this: GlobalEventHandlers, ev: MouseEvent) => any) | null; - /** - * Fires when the user moves the mouse pointer into the object. - * @param ev The mouse event. + /** + * Fires when the user moves the mouse pointer into the object. + * @param ev The mouse event. */ onmouseover: ((this: GlobalEventHandlers, ev: MouseEvent) => any) | null; - /** - * Fires when the user releases a mouse button while the mouse is over the object. - * @param ev The mouse event. + /** + * Fires when the user releases a mouse button while the mouse is over the object. + * @param ev The mouse event. */ onmouseup: ((this: GlobalEventHandlers, ev: MouseEvent) => any) | null; - /** - * Occurs when playback is paused. - * @param ev The event. + /** + * Occurs when playback is paused. + * @param ev The event. */ onpause: ((this: GlobalEventHandlers, ev: Event) => any) | null; - /** - * Occurs when the play method is requested. - * @param ev The event. + /** + * Occurs when the play method is requested. + * @param ev The event. */ onplay: ((this: GlobalEventHandlers, ev: Event) => any) | null; - /** - * Occurs when the audio or video has started playing. - * @param ev The event. + /** + * Occurs when the audio or video has started playing. + * @param ev The event. */ onplaying: ((this: GlobalEventHandlers, ev: Event) => any) | null; onpointercancel: ((this: GlobalEventHandlers, ev: PointerEvent) => any) | null; @@ -5724,59 +5894,59 @@ interface GlobalEventHandlers { onpointerout: ((this: GlobalEventHandlers, ev: PointerEvent) => any) | null; onpointerover: ((this: GlobalEventHandlers, ev: PointerEvent) => any) | null; onpointerup: ((this: GlobalEventHandlers, ev: PointerEvent) => any) | null; - /** - * Occurs to indicate progress while downloading media data. - * @param ev The event. + /** + * Occurs to indicate progress while downloading media data. + * @param ev The event. */ onprogress: ((this: GlobalEventHandlers, ev: ProgressEvent) => any) | null; - /** - * Occurs when the playback rate is increased or decreased. - * @param ev The event. + /** + * Occurs when the playback rate is increased or decreased. + * @param ev The event. */ onratechange: ((this: GlobalEventHandlers, ev: Event) => any) | null; - /** - * Fires when the user resets a form. - * @param ev The event. + /** + * Fires when the user resets a form. + * @param ev The event. */ onreset: ((this: GlobalEventHandlers, ev: Event) => any) | null; onresize: ((this: GlobalEventHandlers, ev: UIEvent) => any) | null; - /** - * Fires when the user repositions the scroll box in the scroll bar on the object. - * @param ev The event. + /** + * Fires when the user repositions the scroll box in the scroll bar on the object. + * @param ev The event. */ onscroll: ((this: GlobalEventHandlers, ev: Event) => any) | null; onsecuritypolicyviolation: ((this: GlobalEventHandlers, ev: SecurityPolicyViolationEvent) => any) | null; - /** - * Occurs when the seek operation ends. - * @param ev The event. + /** + * Occurs when the seek operation ends. + * @param ev The event. */ onseeked: ((this: GlobalEventHandlers, ev: Event) => any) | null; - /** - * Occurs when the current playback position is moved. - * @param ev The event. + /** + * Occurs when the current playback position is moved. + * @param ev The event. */ onseeking: ((this: GlobalEventHandlers, ev: Event) => any) | null; - /** - * Fires when the current selection changes. - * @param ev The event. + /** + * Fires when the current selection changes. + * @param ev The event. */ onselect: ((this: GlobalEventHandlers, ev: Event) => any) | null; onselectionchange: ((this: GlobalEventHandlers, ev: Event) => any) | null; onselectstart: ((this: GlobalEventHandlers, ev: Event) => any) | null; - /** - * Occurs when the download has stopped. - * @param ev The event. + /** + * Occurs when the download has stopped. + * @param ev The event. */ onstalled: ((this: GlobalEventHandlers, ev: Event) => any) | null; onsubmit: ((this: GlobalEventHandlers, ev: Event) => any) | null; - /** - * Occurs if the load operation has been intentionally halted. - * @param ev The event. + /** + * Occurs if the load operation has been intentionally halted. + * @param ev The event. */ onsuspend: ((this: GlobalEventHandlers, ev: Event) => any) | null; - /** - * Occurs to indicate the current playback position. - * @param ev The event. + /** + * Occurs to indicate the current playback position. + * @param ev The event. */ ontimeupdate: ((this: GlobalEventHandlers, ev: Event) => any) | null; ontoggle: ((this: GlobalEventHandlers, ev: Event) => any) | null; @@ -5788,14 +5958,14 @@ interface GlobalEventHandlers { ontransitionend: ((this: GlobalEventHandlers, ev: TransitionEvent) => any) | null; ontransitionrun: ((this: GlobalEventHandlers, ev: TransitionEvent) => any) | null; ontransitionstart: ((this: GlobalEventHandlers, ev: TransitionEvent) => any) | null; - /** - * Occurs when the volume is changed, or playback is muted or unmuted. - * @param ev The event. + /** + * Occurs when the volume is changed, or playback is muted or unmuted. + * @param ev The event. */ onvolumechange: ((this: GlobalEventHandlers, ev: Event) => any) | null; - /** - * Occurs when playback stops because the next frame of a video resource is not available. - * @param ev The event. + /** + * Occurs when playback stops because the next frame of a video resource is not available. + * @param ev The event. */ onwaiting: ((this: GlobalEventHandlers, ev: Event) => any) | null; onwheel: ((this: GlobalEventHandlers, ev: WheelEvent) => any) | null; @@ -5805,21 +5975,21 @@ interface GlobalEventHandlers { removeEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void; } -interface GlobalFetch { - fetch(input: RequestInfo, init?: RequestInit): Promise; -} - interface HTMLAllCollection { /** * Returns the number of elements in the collection. */ readonly length: number; /** - * element = collection(index) + * Returns the item with index index from the collection (determined by tree order). */ item(nameOrIndex?: string): HTMLCollection | Element | null; /** - * element = collection(name) + * Returns the item with ID or name name from the collection. + * + * If there are multiple matching items, then an HTMLCollection object containing all those elements is returned. + * + * Only button, form, iframe, input, map, meta, object, select, and textarea elements can have a name for the purpose of this method; their name is given by the value of their name attribute. */ namedItem(name: string): HTMLCollection | Element | null; [index: number]: Element; @@ -5832,49 +6002,49 @@ declare var HTMLAllCollection: { /** Hyperlink elements and provides special properties and methods (beyond those of the regular HTMLElement object interface that they inherit from) for manipulating the layout and presentation of such elements. */ interface HTMLAnchorElement extends HTMLElement, HTMLHyperlinkElementUtils { - /** - * Sets or retrieves the character set used to encode the object. + /** + * Sets or retrieves the character set used to encode the object. */ /** @deprecated */ charset: string; - /** - * Sets or retrieves the coordinates of the object. + /** + * Sets or retrieves the coordinates of the object. */ /** @deprecated */ coords: string; download: string; - /** - * Sets or retrieves the language code of the object. + /** + * Sets or retrieves the language code of the object. */ hreflang: string; - /** - * Sets or retrieves the shape of the object. + /** + * Sets or retrieves the shape of the object. */ /** @deprecated */ name: string; ping: string; referrerPolicy: string; - /** - * Sets or retrieves the relationship between the object and the destination of the link. + /** + * Sets or retrieves the relationship between the object and the destination of the link. */ rel: string; readonly relList: DOMTokenList; - /** - * Sets or retrieves the relationship between the object and the destination of the link. + /** + * Sets or retrieves the relationship between the object and the destination of the link. */ /** @deprecated */ rev: string; - /** - * Sets or retrieves the shape of the object. + /** + * Sets or retrieves the shape of the object. */ /** @deprecated */ shape: string; - /** - * Sets or retrieves the window or frame at which to target content. + /** + * Sets or retrieves the window or frame at which to target content. */ target: string; - /** - * Retrieves or sets the text of the object as a string. + /** + * Retrieves or sets the text of the object as a string. */ text: string; type: string; @@ -5892,33 +6062,33 @@ declare var HTMLAnchorElement: { interface HTMLAppletElement extends HTMLElement { /** @deprecated */ align: string; - /** - * Sets or retrieves a text alternative to the graphic. + /** + * Sets or retrieves a text alternative to the graphic. */ /** @deprecated */ alt: string; - /** - * Sets or retrieves a character string that can be used to implement your own archive functionality for the object. + /** + * Sets or retrieves a character string that can be used to implement your own archive functionality for the object. */ /** @deprecated */ archive: string; /** @deprecated */ code: string; - /** - * Sets or retrieves the URL of the component. + /** + * Sets or retrieves the URL of the component. */ /** @deprecated */ codeBase: string; readonly form: HTMLFormElement | null; - /** - * Sets or retrieves the height of the object. + /** + * Sets or retrieves the height of the object. */ /** @deprecated */ height: string; /** @deprecated */ hspace: number; - /** - * Sets or retrieves the shape of the object. + /** + * Sets or retrieves the shape of the object. */ /** @deprecated */ name: string; @@ -5941,17 +6111,17 @@ declare var HTMLAppletElement: { /** Provides special properties and methods (beyond those of the regular object HTMLElement interface it also has available to it by inheritance) for manipulating the layout and presentation of elements. */ interface HTMLAreaElement extends HTMLElement, HTMLHyperlinkElementUtils { - /** - * Sets or retrieves a text alternative to the graphic. + /** + * Sets or retrieves a text alternative to the graphic. */ alt: string; - /** - * Sets or retrieves the coordinates of the object. + /** + * Sets or retrieves the coordinates of the object. */ coords: string; download: string; - /** - * Sets or gets whether clicks in this region cause action. + /** + * Sets or gets whether clicks in this region cause action. */ /** @deprecated */ noHref: boolean; @@ -5959,12 +6129,12 @@ interface HTMLAreaElement extends HTMLElement, HTMLHyperlinkElementUtils { referrerPolicy: string; rel: string; readonly relList: DOMTokenList; - /** - * Sets or retrieves the shape of the object. + /** + * Sets or retrieves the shape of the object. */ shape: string; - /** - * Sets or retrieves the window or frame at which to target content. + /** + * Sets or retrieves the window or frame at which to target content. */ target: string; addEventListener(type: K, listener: (this: HTMLAreaElement, ev: HTMLElementEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void; @@ -5993,8 +6163,8 @@ declare var HTMLAudioElement: { /** A HTML line break element (
). It inherits from HTMLElement. */ interface HTMLBRElement extends HTMLElement { - /** - * Sets or retrieves the side on which floating objects are not to be positioned when any IHTMLBlockElement is inserted into the document. + /** + * Sets or retrieves the side on which floating objects are not to be positioned when any IHTMLBlockElement is inserted into the document. */ /** @deprecated */ clear: string; @@ -6011,12 +6181,12 @@ declare var HTMLBRElement: { /** Contains the base URI for a document. This object inherits all of the properties and methods as described in the HTMLElement interface. */ interface HTMLBaseElement extends HTMLElement { - /** - * Gets or sets the baseline URL on which relative links are based. + /** + * Gets or sets the baseline URL on which relative links are based. */ href: string; - /** - * Sets or retrieves the window or frame at which to target content. + /** + * Sets or retrieves the window or frame at which to target content. */ target: string; addEventListener(type: K, listener: (this: HTMLBaseElement, ev: HTMLElementEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void; @@ -6032,13 +6202,13 @@ declare var HTMLBaseElement: { /** Provides special properties (beyond the regular HTMLElement interface it also has available to it by inheritance) for manipulating elements. */ interface HTMLBaseFontElement extends HTMLElement, DOML2DeprecatedColorProperty { - /** - * Sets or retrieves the current typeface family. + /** + * Sets or retrieves the current typeface family. */ /** @deprecated */ face: string; - /** - * Sets or retrieves the font size of the object. + /** + * Sets or retrieves the font size of the object. */ /** @deprecated */ size: number; @@ -6089,68 +6259,68 @@ declare var HTMLBodyElement: { /** Provides properties and methods (beyond the regular HTMLElement interface it also has available to it by inheritance) for manipulating